├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── CouchbaseLitePlugin.h │ ├── CouchbaseLitePlugin.m │ ├── QueryEventListener.swift │ ├── DatabaseEventListener.swift │ ├── ReplicatorEventListener.swift │ └── DataConverter.swift ├── .gitignore └── couchbase_lite.podspec ├── android ├── settings.gradle ├── gradle.properties ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── saltechsystems │ │ └── couchbase_lite │ │ ├── CBManagerDelegate.java │ │ ├── QueryEventListener.java │ │ ├── DatabaseEventListener.java │ │ └── ReplicationEventListener.java ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── build.gradle ├── 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.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Podfile ├── android │ ├── gradle.properties │ ├── 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 │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── xml │ │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── saltechsystems │ │ │ │ │ │ └── couchbase_lite_example │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── lib │ ├── models │ │ ├── README.md │ │ ├── api │ │ │ ├── serializers.g.dart │ │ │ ├── serializers.dart │ │ │ └── token_response.dart │ │ ├── database │ │ │ ├── serializers.g.dart │ │ │ ├── serializers.dart │ │ │ ├── beer.dart │ │ │ ├── brewery.dart │ │ │ ├── brewery.g.dart │ │ │ └── beer.g.dart │ │ └── serializers │ │ │ └── boolean_serializer.dart │ ├── colors.dart │ ├── widgets │ │ ├── beer_list_item.dart │ │ └── brewery_list_item.dart │ ├── beer_sample_app.dart │ ├── data │ │ ├── observable_response.dart │ │ ├── api_provider.dart │ │ └── repository.dart │ └── main.dart ├── sync-gateway-config.json ├── test │ ├── widget_test.dart │ ├── http_utils.dart │ └── api_server_utils.dart ├── README.md ├── .gitignore └── pubspec.yaml ├── lib ├── src │ ├── query │ │ ├── limit.dart │ │ ├── expression │ │ │ ├── meta.dart │ │ │ ├── meta_expression.dart │ │ │ ├── property_expression.dart │ │ │ ├── variable_expression.dart │ │ │ ├── array_expression_in.dart │ │ │ ├── full_text_expression.dart │ │ │ ├── array_expression.dart │ │ │ ├── array_expression_satisfies.dart │ │ │ └── expression.dart │ │ ├── result_set.dart │ │ ├── order_by.dart │ │ ├── select.dart │ │ ├── query_builder.dart │ │ ├── having.dart │ │ ├── joins.dart │ │ ├── group_by.dart │ │ ├── where.dart │ │ ├── parameters.dart │ │ ├── ordering.dart │ │ ├── array_functions.dart │ │ ├── join.dart │ │ ├── from.dart │ │ ├── select_result.dart │ │ ├── result.dart │ │ └── query.dart │ ├── log.dart │ ├── README.md │ ├── listener_token.dart │ ├── serializers.g.dart │ ├── authenticator.dart │ ├── serializers.dart │ ├── replicated_document.dart │ ├── document_replication.dart │ ├── blob.dart │ ├── index.dart │ ├── replicator_configuration.dart │ ├── fragment.dart │ ├── mutable_document.dart │ ├── replicator.dart │ └── document.dart └── couchbase_lite.dart ├── .gitignore ├── .metadata ├── .github ├── workflows │ ├── flutter.yaml │ └── publish.yaml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .travis.yml ├── analysis_options.yaml ├── LICENSE ├── couchbase_lite.iml ├── pubspec.yaml ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md └── test └── document_test.dart /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'couchbase_lite' 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/src/query/limit.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Limit extends Query {} 4 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true -------------------------------------------------------------------------------- /ios/Classes/CouchbaseLitePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface CouchbaseLitePlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | .idea/ 4 | 5 | .packages 6 | .pub/ 7 | pubspec.lock 8 | 9 | doc/ 10 | build/ 11 | .idea/workspace.xml 12 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaltechSystems/couchbase_lite/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/SaltechSystems/couchbase_lite/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/query/expression/meta.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Meta { 4 | static MetaExpression get id => MetaExpression({'meta': 'id'}); 5 | static MetaExpression get sequence => MetaExpression({'meta': 'sequence'}); 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Classes/CouchbaseLitePlugin.m: -------------------------------------------------------------------------------- 1 | #import "CouchbaseLitePlugin.h" 2 | #import 3 | 4 | @implementation CouchbaseLitePlugin 5 | + (void)registerWithRegistrar:(NSObject*)registrar { 6 | [SwiftCouchbaseLitePlugin registerWithRegistrar:registrar]; 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /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: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/src/main/java/com/saltechsystems/couchbase_lite/CBManagerDelegate.java: -------------------------------------------------------------------------------- 1 | package com.saltechsystems.couchbase_lite; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | 6 | public interface CBManagerDelegate { 7 | public String lookupKeyForAsset(String asset); 8 | public AssetManager getAssets(); 9 | public Context getContext(); 10 | } 11 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /lib/src/query/result_set.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ResultSet extends Object with IterableMixin { 4 | ResultSet(List _list) { 5 | _internalState = _list; 6 | } 7 | 8 | late List _internalState; 9 | 10 | List allResults() => List.of(_internalState); 11 | 12 | @override 13 | Iterator get iterator => _internalState.iterator; 14 | } 15 | -------------------------------------------------------------------------------- /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/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/log.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | enum LogLevel { debug, verbose, info, warning, error, none } 4 | 5 | class Log{ 6 | static const MethodChannel _methodChannel = 7 | MethodChannel('com.saltechsystems.couchbase_lite/database'); 8 | 9 | set level(LogLevel level) { 10 | _methodChannel.invokeMethod('setConsoleLogLevel', {'level': level.toString().split('.').last}); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/query/order_by.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class OrderBy extends Query { 4 | Limit limit(Expression expression, {Expression? offset}) { 5 | var resultQuery = Limit(); 6 | resultQuery._options = options; 7 | if (offset != null) { 8 | resultQuery._options['limit'] = [expression, offset]; 9 | } else { 10 | resultQuery._options['limit'] = [expression]; 11 | } 12 | return resultQuery; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/query/select.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Select extends Query { 4 | From from(String databaseName, {String? as}) { 5 | var resultQuery = From(); 6 | resultQuery._options = options; 7 | if (as != null) { 8 | resultQuery._options['from'] = {'database': databaseName, 'as': as}; 9 | } else { 10 | resultQuery._options['from'] = {'database': databaseName}; 11 | } 12 | return resultQuery; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/README.md: -------------------------------------------------------------------------------- 1 | ## [built_value](https://pub.dartlang.org/packages/built_value) 2 | 3 | ## [Json to Dart built_value class converter](https://charafau.github.io/json2builtvalue/) 4 | 5 | ##Commands to generate the .g file 6 | flutter packages pub run build_runner build --delete-conflicting-outputs 7 | flutter packages pub run build_runner watch --delete-conflicting-outputs 8 | 9 | ##If you move files around and it gets really messed up you can run this 10 | flutter packages pub run build_runner clean -------------------------------------------------------------------------------- /example/lib/models/README.md: -------------------------------------------------------------------------------- 1 | ## [built_value](https://pub.dartlang.org/packages/built_value) 2 | 3 | ## [Json to Dart built_value class converter](https://charafau.github.io/json2builtvalue/) 4 | 5 | ##Commands to generate the .g file 6 | flutter packages pub run build_runner build --delete-conflicting-outputs 7 | flutter packages pub run build_runner watch --delete-conflicting-outputs 8 | 9 | ##If you move files around and it gets really messed up you can run this 10 | flutter packages pub run build_runner clean -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/saltechsystems/couchbase_lite_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.saltechsystems.couchbase_lite_example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /lib/src/query/expression/meta_expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class MetaExpression extends Object with Expression { 4 | MetaExpression(Map _passedInternalExpression) { 5 | _internalExpressionStack.add(_passedInternalExpression); 6 | } 7 | 8 | MetaExpression._clone(MetaExpression expression) { 9 | _internalExpressionStack.addAll(expression.internalExpressionStack); 10 | } 11 | 12 | @override 13 | MetaExpression _clone() { 14 | return MetaExpression._clone(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/query/expression/property_expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class PropertyExpression extends Object with Expression { 4 | PropertyExpression(Map _passedInternalExpression) { 5 | _internalExpressionStack.add(_passedInternalExpression); 6 | } 7 | 8 | PropertyExpression._clone(PropertyExpression expression) { 9 | _internalExpressionStack.addAll(expression.internalExpressionStack); 10 | } 11 | 12 | @override 13 | PropertyExpression _clone() { 14 | return PropertyExpression._clone(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/query/expression/variable_expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class VariableExpression extends Object with Expression { 4 | VariableExpression(Map _passedInternalExpression) { 5 | _internalExpressionStack.add(_passedInternalExpression); 6 | } 7 | 8 | VariableExpression._clone(VariableExpression expression) { 9 | _internalExpressionStack.addAll(expression.internalExpressionStack); 10 | } 11 | 12 | @override 13 | VariableExpression _clone() { 14 | return VariableExpression._clone(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/query/query_builder.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class QueryBuilder { 4 | static Select select(List _selectResult) { 5 | var query = Select(); 6 | query._options['selectDistinct'] = false; 7 | query._options['selectResult'] = _selectResult; 8 | return query; 9 | } 10 | 11 | static Select selectDistinct(List _selectResult) { 12 | var query = Select(); 13 | query._options['selectDistinct'] = true; 14 | query._options['selectResult'] = _selectResult; 15 | return query; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/listener_token.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ListenerToken { 4 | /// Listener token returned when adding a change listener. The token is used for removing the added change listener. 5 | ListenerToken(); 6 | 7 | final tokenId = Uuid().v1(); 8 | 9 | Map toJson() => {'token': tokenId}; 10 | 11 | @override 12 | bool operator ==(other) { 13 | if (other is ListenerToken) { 14 | return tokenId == other.tokenId; 15 | } 16 | 17 | return false; 18 | } 19 | 20 | @override 21 | int get hashCode => tokenId.hashCode; 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost 5 | 10.0.2.2 6 | 10.0.3.2 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/flutter.yaml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | # container: 15 | # image: google/dart:latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: subosito/flutter-action@v1 20 | with: 21 | channel: 'stable' 22 | - name: Install dependencies 23 | run: flutter pub get 24 | - name: Run tests 25 | run: flutter test 26 | - name: Publish dry run 27 | run: flutter pub publish --dry-run -------------------------------------------------------------------------------- /android/src/main/java/com/saltechsystems/couchbase_lite/QueryEventListener.java: -------------------------------------------------------------------------------- 1 | package com.saltechsystems.couchbase_lite; 2 | 3 | import io.flutter.plugin.common.EventChannel; 4 | 5 | public class QueryEventListener implements EventChannel.StreamHandler { 6 | public EventChannel.EventSink mEventSink; 7 | 8 | /* 9 | * IMPLEMENTATION OF EVENTCHANNEL.STREAMHANDLER 10 | */ 11 | 12 | @Override 13 | public void onListen(Object args, final EventChannel.EventSink eventSink) { 14 | mEventSink = eventSink; 15 | } 16 | 17 | @Override 18 | public void onCancel(Object args) { 19 | mEventSink = null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Classes/QueryEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryEventListener.swift 3 | // 4 | // Created by Saltech Systems on 5/1/19. 5 | // 6 | 7 | import Foundation 8 | import CouchbaseLiteSwift 9 | 10 | class QueryEventListener: FlutterStreamHandler { 11 | var mEventSink: FlutterEventSink? 12 | 13 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 14 | mEventSink = events 15 | 16 | return nil 17 | } 18 | 19 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 20 | mEventSink = nil 21 | 22 | return nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/saltechsystems/couchbase_lite/DatabaseEventListener.java: -------------------------------------------------------------------------------- 1 | package com.saltechsystems.couchbase_lite; 2 | 3 | import io.flutter.plugin.common.EventChannel; 4 | 5 | public class DatabaseEventListener implements EventChannel.StreamHandler { 6 | public EventChannel.EventSink mEventSink; 7 | 8 | /* 9 | * IMPLEMENTATION OF EVENTCHANNEL.STREAMHANDLER 10 | */ 11 | 12 | @Override 13 | public void onListen(Object args, final EventChannel.EventSink eventSink) { 14 | mEventSink = eventSink; 15 | } 16 | 17 | @Override 18 | public void onCancel(Object args) { 19 | mEventSink = null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Classes/DatabaseEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatabaseEventListener.swift 3 | // couchbase_lite 4 | // 5 | // Created by Kin Mak on 8/7/2020. 6 | // 7 | 8 | import Foundation 9 | import CouchbaseLiteSwift 10 | 11 | class DatabaseEventListener: FlutterStreamHandler { 12 | var mEventSink: FlutterEventSink? 13 | 14 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 15 | mEventSink = events 16 | 17 | return nil 18 | } 19 | 20 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 21 | 22 | 23 | return nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/query/having.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Having extends Query { 4 | Limit limit(Expression expression, {Expression? offset}) { 5 | var resultQuery = Limit(); 6 | resultQuery._options = options; 7 | if (offset != null) { 8 | resultQuery._options['limit'] = [expression, offset]; 9 | } else { 10 | resultQuery._options['limit'] = [expression]; 11 | } 12 | return resultQuery; 13 | } 14 | 15 | OrderBy orderBy(List orderingList) { 16 | var resultQuery = OrderBy(); 17 | resultQuery._options = options; 18 | resultQuery._options['orderBy'] = orderingList; 19 | return resultQuery; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | sudo: false 4 | addons: 5 | apt: 6 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 7 | sources: 8 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version 9 | packages: 10 | - libstdc++6 11 | - fonts-noto 12 | before_script: 13 | - git clone https://github.com/flutter/flutter.git -b stable 14 | - ./flutter/bin/flutter doctor 15 | - gem install coveralls-lcov 16 | script: 17 | - ./flutter/bin/cache/dart-sdk/bin/dartfmt -n --set-exit-if-changed ./lib ./test ./example || travis_terminate 1 18 | - ./flutter/bin/flutter test --coverage 19 | after_success: 20 | - coveralls-lcov coverage/lcov.info 21 | cache: 22 | directories: 23 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /example/sync-gateway-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": ["*"], 3 | "databases": { 4 | "beer-sample": { 5 | "server": "http://localhost:8091", 6 | "bucket": "beer-sample", 7 | "username": "sync_gateway", 8 | "password": "password", 9 | "enable_shared_bucket_access": true, 10 | "import_docs": true, 11 | "num_index_replicas": 0, 12 | "users": { 13 | "GUEST": { "disabled": false, "admin_channels": ["*"] }, 14 | "foo": { 15 | "password": "bar", 16 | "admin_channels": ["*"], 17 | "admin_roles": [], 18 | "disabled": false 19 | } 20 | }, 21 | "sync": `function (doc, oldDoc) { 22 | if (doc.sdk) { 23 | channel(doc.sdk); 24 | } 25 | }` 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/models/api/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of serializers; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializers _$serializers = 10 | (new Serializers().toBuilder()..add(TokenResponse.serializer)).build(); 11 | 12 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new 13 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/pedantic#enabled-lints. 4 | include: package:pedantic/analysis_options.yaml 5 | 6 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 7 | # Uncomment to specify additional rules. 8 | linter: 9 | rules: 10 | - always_declare_return_types 11 | - camel_case_types 12 | - empty_constructor_bodies 13 | - annotate_overrides 14 | - avoid_init_to_null 15 | - constant_identifier_names 16 | - one_member_abstracts 17 | - slash_for_doc_comments 18 | - sort_constructors_first 19 | - unnecessary_brace_in_string_interps 20 | - non_constant_identifier_names 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** -------------------------------------------------------------------------------- /lib/src/query/expression/array_expression_in.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ArrayExpressionIn extends Object with Expression { 4 | ArrayExpressionIn(this.type, this.variable) { 5 | this._internalExpressionStack.addAll(variable.internalExpressionStack); 6 | } 7 | 8 | ArrayExpressionIn._clone(ArrayExpressionIn expression) 9 | : type = expression.type, 10 | this.variable = expression.variable { 11 | this._internalExpressionStack.addAll(variable.internalExpressionStack); 12 | } 13 | 14 | final String type; 15 | final VariableExpression variable; 16 | 17 | ArrayExpressionSatisfies inA(Expression inExpression) { 18 | return ArrayExpressionSatisfies(type, variable, inExpression); 19 | } 20 | 21 | @override 22 | ArrayExpressionIn _clone() { 23 | return ArrayExpressionIn._clone(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/models/database/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of serializers; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializers _$serializers = (new Serializers().toBuilder() 10 | ..add(Beer.serializer) 11 | ..add(Brewery.serializer)) 12 | .build(); 13 | 14 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new 15 | -------------------------------------------------------------------------------- /lib/src/query/joins.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Joins extends Query { 4 | Limit limit(Expression expression, {Expression? offset}) { 5 | var resultQuery = Limit(); 6 | resultQuery._options = options; 7 | if (offset != null) { 8 | resultQuery._options['limit'] = [expression, offset]; 9 | } else { 10 | resultQuery._options['limit'] = [expression]; 11 | } 12 | return resultQuery; 13 | } 14 | 15 | OrderBy orderBy(List orderingList) { 16 | var resultQuery = OrderBy(); 17 | resultQuery._options = options; 18 | resultQuery._options['orderBy'] = orderingList; 19 | return resultQuery; 20 | } 21 | 22 | Where where(Expression expression) { 23 | var resultQuery = Where(); 24 | resultQuery._options = options; 25 | resultQuery._options['where'] = expression; 26 | return resultQuery; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/query/group_by.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class GroupBy extends Query { 4 | Limit limit(Expression expression, {Expression? offset}) { 5 | var resultQuery = Limit(); 6 | resultQuery._options = options; 7 | if (offset != null) { 8 | resultQuery._options['limit'] = [expression, offset]; 9 | } else { 10 | resultQuery._options['limit'] = [expression]; 11 | } 12 | return resultQuery; 13 | } 14 | 15 | OrderBy orderBy(List orderingList) { 16 | var resultQuery = OrderBy(); 17 | resultQuery._options = options; 18 | resultQuery._options['orderBy'] = orderingList; 19 | return resultQuery; 20 | } 21 | 22 | Having having(Expression expression) { 23 | var resultQuery = Having(); 24 | resultQuery._options = options; 25 | resultQuery._options['having'] = expression; 26 | return resultQuery; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/query/where.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Where extends Query { 4 | Limit limit(Expression expression, {Expression? offset}) { 5 | var resultQuery = Limit(); 6 | resultQuery._options = options; 7 | if (offset != null) { 8 | resultQuery._options['limit'] = [expression, offset]; 9 | } else { 10 | resultQuery._options['limit'] = [expression]; 11 | } 12 | return resultQuery; 13 | } 14 | 15 | OrderBy orderBy(List orderingList) { 16 | var resultQuery = OrderBy(); 17 | resultQuery._options = options; 18 | resultQuery._options['orderBy'] = orderingList; 19 | return resultQuery; 20 | } 21 | 22 | GroupBy groupBy(List expressionList) { 23 | var resultQuery = GroupBy(); 24 | resultQuery._options = options; 25 | resultQuery._options['groupBy'] = expressionList; 26 | return resultQuery; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.saltechsystems.couchbase_lite' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:4.0.2' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | maven { 20 | url "https://maven.google.com" 21 | } 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | defaultConfig { 31 | minSdkVersion 19 32 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 33 | } 34 | lintOptions { 35 | disable 'InvalidPackage' 36 | } 37 | dependencies { 38 | implementation 'com.couchbase.lite:couchbase-lite-android:2.8.6' 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/colors.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018-present the Flutter authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/material.dart'; 16 | 17 | const kPrimaryColor = Color(0xFF05538E); 18 | const kAccentColor = Color(0xFF17A4E6); 19 | const kErrorRed = Color(0xFFC5032B); 20 | const kSurfaceWhite = Color(0xFFFFFBFA); 21 | const kBackgroundWhite = Colors.white; 22 | -------------------------------------------------------------------------------- /lib/src/query/parameters.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Parameters { 4 | /*Map map; 5 | 6 | dynamic getValue(String name) { 7 | if (map.containsKey(name)) { 8 | return map[name]; 9 | } else { 10 | return null; 11 | } 12 | } 13 | 14 | Parameters setBoolean(String name, bool value) { 15 | map[name] = value; 16 | return this; 17 | } 18 | 19 | Parameters setDate(String name, DateTime value) { 20 | map[name] = value; 21 | return this; 22 | } 23 | 24 | Parameters setDouble(String name, double value) { 25 | map[name] = value; 26 | return this; 27 | } 28 | 29 | Parameters setInt(String name, int value) { 30 | map[name] = value; 31 | return this; 32 | } 33 | 34 | Parameters setString(String name, String value) { 35 | map[name] = value; 36 | return this; 37 | } 38 | 39 | Parameters setValue(String name, dynamic value) { 40 | map[name] = value; 41 | return this; 42 | }*/ 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of serializers; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializers _$serializers = (new Serializers().toBuilder() 10 | ..add(DocumentReplication.serializer) 11 | ..add(ReplicatedDocument.serializer) 12 | ..addBuilderFactory( 13 | const FullType(BuiltList, const [const FullType(ReplicatedDocument)]), 14 | () => new ListBuilder())) 15 | .build(); 16 | 17 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /lib/src/query/ordering.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Ordering { 4 | Ordering._internal(Expression _expression) { 5 | _internalExpression = _expression; 6 | } 7 | 8 | factory Ordering.property(String _property) { 9 | return Ordering._internal(Expression.property(_property)); 10 | } 11 | 12 | factory Ordering.expression(Expression _expression) { 13 | return Ordering._internal(_expression); 14 | } 15 | 16 | late Expression _internalExpression; 17 | 18 | Ordering ascending() { 19 | var clone = _internalExpression._clone(); 20 | clone._internalExpressionStack.add({'orderingSortOrder': 'ascending'}); 21 | return Ordering._internal(clone); 22 | } 23 | 24 | Ordering descending() { 25 | var clone = _internalExpression._clone(); 26 | clone._internalExpressionStack.add({'orderingSortOrder': 'descending'}); 27 | return Ordering._internal(clone); 28 | } 29 | 30 | List> toJson() { 31 | return _internalExpression.internalExpressionStack; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | // ignore: avoid_relative_lib_imports 12 | import '../lib/beer_sample_app.dart'; 13 | 14 | void main() { 15 | testWidgets('Verify Platform version', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | var app = BeerSampleApp(AppMode.production); 18 | await tester.pumpWidget(app); 19 | 20 | // Verify that platform version is retrieved. 21 | expect( 22 | find.byWidgetPredicate( 23 | (Widget widget) => 24 | widget is Text && widget.data!.startsWith('Initializing'), 25 | ), 26 | findsOneWidget, 27 | ); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/query/array_functions.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ArrayFunctions with Expression { 4 | ArrayFunctions(Map _passedInternalExpression) { 5 | this._internalExpressionStack.add(_passedInternalExpression); 6 | } 7 | 8 | ArrayFunctions._clone(ArrayFunctions expression) { 9 | this._internalExpressionStack.addAll(expression.internalExpressionStack); 10 | } 11 | 12 | factory ArrayFunctions.length(Expression expression) { 13 | return ArrayFunctions({ 14 | 'arrayLength': expression.internalExpressionStack, 15 | }); 16 | } 17 | 18 | factory ArrayFunctions.contains(Expression expression, Expression value) { 19 | return ArrayFunctions({ 20 | 'arrayContains': expression.internalExpressionStack, 21 | 'value': value.internalExpressionStack, 22 | }); 23 | } 24 | 25 | @override 26 | final List> _internalExpressionStack = []; 27 | 28 | @override 29 | List> get internalExpressionStack => List.from(_internalExpressionStack); 30 | 31 | @override 32 | ArrayFunctions _clone() { 33 | return ArrayFunctions._clone(this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Luca Christille 4 | Copyright (c) 2019 Saltech Systems 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/src/query/expression/full_text_expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class FullTextExpressionIndex { 4 | final String _name; 5 | FullTextExpressionIndex._(this._name); 6 | 7 | FullTextExpression match(String query) { 8 | return FullTextExpression._match(_name, query); 9 | } 10 | } 11 | 12 | class FullTextExpression extends Object with Expression { 13 | static FullTextExpressionIndex index(String name) { 14 | return FullTextExpressionIndex._(name); 15 | } 16 | 17 | factory FullTextExpression.rank(String indexName) { 18 | final expression = FullTextExpression._(); 19 | expression._internalExpressionStack.add({'rank': indexName}); 20 | return expression; 21 | } 22 | 23 | FullTextExpression._(); 24 | 25 | FullTextExpression._match(String indexName, String query) { 26 | _internalExpressionStack.add({ 27 | 'fullTextMatch': [indexName, query], 28 | }); 29 | } 30 | 31 | FullTextExpression._clone(FullTextExpression expression) { 32 | _internalExpressionStack.addAll(expression.internalExpressionStack); 33 | } 34 | 35 | @override 36 | FullTextExpression _clone() { 37 | return FullTextExpression._clone(this); 38 | } 39 | } -------------------------------------------------------------------------------- /lib/src/authenticator.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | /// Authenticator objects provide server authentication credentials to the replicator. 4 | abstract class Authenticator {} 5 | 6 | class BasicAuthenticator implements Authenticator { 7 | /// The BasicAuthenticator class is an authenticator that will authenticate using HTTP Basic auth with the given [username] and [password]. 8 | BasicAuthenticator(this.username, this.password); 9 | 10 | final String username; 11 | final String password; 12 | 13 | Map toJson() { 14 | return { 15 | 'method': 'basic', 16 | 'username': username, 17 | 'password': password, 18 | }; 19 | } 20 | } 21 | 22 | class SessionAuthenticator implements Authenticator { 23 | /// The SessionAuthenticator class is an authenticator that will authenticate by using the [sessionId] of the session created by a Sync Gateway. 24 | SessionAuthenticator(this.sessionId, {this.cookieName}); 25 | 26 | final String sessionId; 27 | final String? cookieName; 28 | 29 | Map toJson() { 30 | return { 31 | 'method': 'session', 32 | 'sessionId': sessionId, 33 | 'cookieName': cookieName, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ios/couchbase_lite.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'couchbase_lite' 6 | s.version = '0.0.1' 7 | s.summary = 'Community edition of Couchbase Lite. Couchbase Lite is an embedded lightweight, document-oriented (NoSQL), syncable database engine.' 8 | s.description = <<-DESC 9 | Community edition of Couchbase Lite. Couchbase Lite is an embedded lightweight, document-oriented (NoSQL), syncable database engine. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'CouchbaseLite-Swift', '~> 2.8.4' 19 | 20 | s.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64'} 21 | s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64'} 22 | s.ios.deployment_target = '11.0' 23 | s.osx.deployment_target = '10.11' 24 | end 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish package to pub.dev 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | # container: 14 | # image: google/dart:latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: subosito/flutter-action@v1 19 | with: 20 | channel: 'stable' 21 | - name: Setup credentials 22 | run: | 23 | mkdir -p $PUB_CACHE 24 | cat < $PUB_CACHE/credentials.json 25 | { 26 | "accessToken":"${{ secrets.PUB_DEV_PUBLISH_ACCESS_TOKEN }}", 27 | "refreshToken":"${{ secrets.PUB_DEV_PUBLISH_REFRESH_TOKEN }}", 28 | "tokenEndpoint":"${{ secrets.PUB_DEV_PUBLISH_TOKEN_ENDPOINT }}", 29 | "scopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email" ], 30 | "expiration": ${{ secrets.PUB_DEV_PUBLISH_EXPIRATION }} 31 | } 32 | EOF 33 | - name: Install dependencies 34 | run: flutter pub get 35 | - name: Document plugin 36 | run: | 37 | pub global activate dartdoc 38 | pub global run dartdoc 39 | - name: Publish plugin 40 | run: flutter pub publish --force 41 | -------------------------------------------------------------------------------- /lib/src/query/join.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Join { 4 | Join._internal(String selector, String _dataSource, {String? as}) { 5 | if (as != null) { 6 | _internalStack.add({selector: _dataSource, 'as': as}); 7 | } else { 8 | _internalStack.add({selector: _dataSource}); 9 | } 10 | } 11 | 12 | factory Join.join(String _dataSource, {String? as}) { 13 | return Join._internal('join', _dataSource, as: as); 14 | } 15 | 16 | factory Join.crossJoin(String _dataSource, {String? as}) { 17 | return Join._internal('crossJoin', _dataSource, as: as); 18 | } 19 | 20 | factory Join.innerJoin(String _dataSource, {String? as}) { 21 | return Join._internal('innerJoin', _dataSource, as: as); 22 | } 23 | 24 | factory Join.leftJoin(String _dataSource, {String? as}) { 25 | return Join._internal('leftJoin', _dataSource, as: as); 26 | } 27 | 28 | factory Join.leftOuterJoin(String _dataSource, {String? as}) { 29 | return Join._internal('leftOuterJoin', _dataSource, as: as); 30 | } 31 | 32 | final List> _internalStack = []; 33 | 34 | Join on(Expression _expression) { 35 | _internalStack.add({'on': _expression}); 36 | return this; 37 | } 38 | 39 | List> toJson() => _internalStack; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/serializers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. 2 | // All rights reserved. Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | library serializers; 6 | 7 | import 'package:built_collection/built_collection.dart'; 8 | import 'package:built_value/serializer.dart'; 9 | import 'package:built_value/standard_json_plugin.dart'; 10 | 11 | import 'replicated_document.dart'; 12 | import 'document_replication.dart'; 13 | 14 | part 'serializers.g.dart'; 15 | 16 | /// Example of how to use built_value serialization. 17 | /// 18 | /// Declare a top level [Serializers] field called serializers. Annotate it 19 | /// with [SerializersFor] and provide a `const` `List` of types you want to 20 | /// be serializable. 21 | /// 22 | /// The built_value code generator will provide the implementation. It will 23 | /// contain serializers for all the types asked for explicitly plus all the 24 | /// types needed transitively via fields. 25 | /// 26 | /// You usually only need to do this once per project. 27 | @SerializersFor([ReplicatedDocument, DocumentReplication]) 28 | Serializers serializers = _$serializers; 29 | 30 | Serializers standardSerializers = 31 | (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); 32 | -------------------------------------------------------------------------------- /lib/src/query/from.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class From extends Query { 4 | Where where(Expression expression) { 5 | var resultQuery = Where(); 6 | resultQuery._options = options; 7 | resultQuery._options['where'] = expression; 8 | return resultQuery; 9 | } 10 | 11 | GroupBy groupBy(List expressionList) { 12 | var resultQuery = GroupBy(); 13 | resultQuery._options = options; 14 | resultQuery._options['groupBy'] = expressionList; 15 | return resultQuery; 16 | } 17 | 18 | Joins join(Join expression) { 19 | var resultQuery = Joins(); 20 | resultQuery._options = options; 21 | resultQuery._options['joins'] = expression; 22 | return resultQuery; 23 | } 24 | 25 | Limit limit(Expression expression, {Expression? offset}) { 26 | var resultQuery = Limit(); 27 | resultQuery._options = options; 28 | if (offset != null) { 29 | resultQuery._options['limit'] = [expression, offset]; 30 | } else { 31 | resultQuery._options['limit'] = [expression]; 32 | } 33 | return resultQuery; 34 | } 35 | 36 | OrderBy orderBy(List orderingList) { 37 | var resultQuery = OrderBy(); 38 | resultQuery._options = options; 39 | resultQuery._options['orderBy'] = orderingList; 40 | return resultQuery; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/replicated_document.dart: -------------------------------------------------------------------------------- 1 | library replicated_document; 2 | 3 | import 'dart:convert'; 4 | 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | 8 | import 'serializers.dart'; 9 | 10 | part 'replicated_document.g.dart'; 11 | 12 | abstract class ReplicatedDocument 13 | implements Built { 14 | ReplicatedDocument._(); 15 | 16 | factory ReplicatedDocument([Function(ReplicatedDocumentBuilder b)? updates]) = 17 | _$ReplicatedDocument; 18 | 19 | @BuiltValueField(wireName: 'document') 20 | String get id; 21 | @BuiltValueField(wireName: 'error') 22 | String? get error; 23 | @BuiltValueField(wireName: 'flags') 24 | int get flags; 25 | 26 | String toJson() { 27 | return json.encode(toMap()); 28 | } 29 | 30 | Map? toMap() { 31 | return standardSerializers.serializeWith( 32 | ReplicatedDocument.serializer, this) as Map?; 33 | } 34 | 35 | static ReplicatedDocument? fromJson(String jsonString) { 36 | return fromMap(json.decode(jsonString)); 37 | } 38 | 39 | static ReplicatedDocument? fromMap(Map? jsonMap) { 40 | return standardSerializers.deserializeWith( 41 | ReplicatedDocument.serializer, jsonMap); 42 | } 43 | 44 | static Serializer get serializer => 45 | _$replicatedDocumentSerializer; 46 | } 47 | -------------------------------------------------------------------------------- /example/lib/widgets/beer_list_item.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:couchbase_lite_example/models/database/beer.dart'; 16 | import 'package:flutter/material.dart'; 17 | 18 | class BeerListItem extends StatelessWidget { 19 | BeerListItem(this.beer, {this.onTap}); 20 | 21 | final Beer? beer; 22 | final VoidCallback? onTap; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return beer == null 27 | ? ListTile(title: Center(child: CircularProgressIndicator())) 28 | : ListTile( 29 | key: ObjectKey(beer), 30 | title: Text( 31 | beer!.name!, 32 | overflow: TextOverflow.ellipsis, 33 | ), 34 | onTap: onTap, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/widgets/brewery_list_item.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:couchbase_lite_example/models/database/brewery.dart'; 16 | import 'package:flutter/material.dart'; 17 | 18 | class BreweryListItem extends StatelessWidget { 19 | BreweryListItem(this.brewery, {this.onTap}); 20 | 21 | final Brewery? brewery; 22 | final VoidCallback? onTap; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return brewery == null 27 | ? ListTile(title: Center(child: CircularProgressIndicator())) 28 | : ListTile( 29 | key: ObjectKey(brewery), 30 | title: Text( 31 | brewery!.name!, 32 | overflow: TextOverflow.ellipsis, 33 | ), 34 | onTap: onTap, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/query/expression/array_expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ArrayExpression extends Object with Expression { 4 | ArrayExpression(Map _passedInternalExpression) { 5 | this._internalExpressionStack.add(_passedInternalExpression); 6 | } 7 | 8 | ArrayExpression._clone(ArrayExpression expression) { 9 | this._internalExpressionStack.addAll(expression.internalExpressionStack); 10 | } 11 | 12 | static const String _quantifiesTypeAny = 'arrayInAny'; 13 | static const String _quantifiesTypeEvery = 'arrayInEvery'; 14 | 15 | static ArrayExpressionIn any(VariableExpression variableExpression) { 16 | if (variableExpression == null) { 17 | throw Exception('variable cannot be null.'); 18 | } 19 | return ArrayExpressionIn(_quantifiesTypeAny, variableExpression); 20 | } 21 | 22 | static ArrayExpressionIn every(VariableExpression variableExpression) { 23 | if (variableExpression == null) { 24 | throw Exception('variable cannot be null.'); 25 | } 26 | return ArrayExpressionIn(_quantifiesTypeEvery, variableExpression); 27 | } 28 | 29 | static VariableExpression variable(String name) { 30 | if (name == null) { 31 | throw Exception('name cannnot be null.'); 32 | } 33 | return VariableExpression({'arrayVariable': name}); 34 | } 35 | 36 | @override 37 | ArrayExpression _clone() { 38 | return ArrayExpression._clone(this); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/models/api/serializers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. 2 | // All rights reserved. Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | library serializers; 6 | 7 | import 'package:built_value/serializer.dart'; 8 | import 'package:built_value/iso_8601_date_time_serializer.dart'; 9 | import 'package:built_value/standard_json_plugin.dart'; 10 | 11 | import 'package:couchbase_lite_example/models/serializers/boolean_serializer.dart'; 12 | import 'package:couchbase_lite_example/models/api/token_response.dart'; 13 | 14 | part 'serializers.g.dart'; 15 | 16 | /// Example of how to use built_value serialization. 17 | /// 18 | /// Declare a top level [Serializers] field called serializers. Annotate it 19 | /// with [SerializersFor] and provide a `const` `List` of types you want to 20 | /// be serializable. 21 | /// 22 | /// The built_value code generator will provide the implementation. It will 23 | /// contain serializers for all the types asked for explicitly plus all the 24 | /// types needed transitively via fields. 25 | /// 26 | /// You usually only need to do this once per project. 27 | @SerializersFor([TokenResponse]) 28 | Serializers serializers = _$serializers; 29 | 30 | Serializers standardSerializers = (serializers.toBuilder() 31 | ..addPlugin(StandardJsonPlugin()) 32 | ..add(Iso8601DateTimeSerializer()) 33 | ..add(BooleanSerializer())) 34 | .build(); 35 | -------------------------------------------------------------------------------- /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 flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/src/query/select_result.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class SelectResult { 4 | static SelectResultFrom all() => SelectResultFrom(Expression.all(), null); 5 | 6 | static SelectResultAs property(String _property) => 7 | expression(Expression.property(_property)); 8 | 9 | static SelectResultAs expression(Expression _expression) => 10 | SelectResultAs(_expression, null); 11 | } 12 | 13 | class SelectResultProtocol { 14 | SelectResultProtocol(Expression expression, {String? alias}) { 15 | this.expression = expression; 16 | this.alias = alias; 17 | } 18 | 19 | late Expression expression; 20 | String? alias; 21 | 22 | List> toJson() { 23 | if (alias != null) { 24 | return expression.internalExpressionStack + 25 | [ 26 | {'as': alias} 27 | ]; 28 | } else { 29 | return expression.internalExpressionStack; 30 | } 31 | } 32 | } 33 | 34 | class SelectResultAs extends SelectResultProtocol { 35 | SelectResultAs(Expression expression, String? alias) 36 | : super(expression, alias: alias); 37 | 38 | SelectResultProtocol as(String _alias) { 39 | return SelectResultProtocol(expression, alias: _alias); 40 | } 41 | } 42 | 43 | class SelectResultFrom extends SelectResultProtocol { 44 | SelectResultFrom(Expression expression, String? alias) 45 | : super(expression, alias: alias); 46 | 47 | SelectResultProtocol from(String _alias) { 48 | return SelectResultProtocol(Expression.all().from(_alias)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/lib/models/database/serializers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. 2 | // All rights reserved. Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | library serializers; 6 | 7 | import 'package:built_value/serializer.dart'; 8 | import 'package:built_value/iso_8601_date_time_serializer.dart'; 9 | import 'package:built_value/standard_json_plugin.dart'; 10 | 11 | import 'package:couchbase_lite_example/models/serializers/boolean_serializer.dart'; 12 | import 'package:couchbase_lite_example/models/database/brewery.dart'; 13 | import 'package:couchbase_lite_example/models/database/beer.dart'; 14 | 15 | part 'serializers.g.dart'; 16 | 17 | /// Example of how to use built_value serialization. 18 | /// 19 | /// Declare a top level [Serializers] field called serializers. Annotate it 20 | /// with [SerializersFor] and provide a `const` `List` of types you want to 21 | /// be serializable. 22 | /// 23 | /// The built_value code generator will provide the implementation. It will 24 | /// contain serializers for all the types asked for explicitly plus all the 25 | /// types needed transitively via fields. 26 | /// 27 | /// You usually only need to do this once per project. 28 | @SerializersFor([Beer, Brewery]) 29 | Serializers serializers = _$serializers; 30 | 31 | Serializers standardSerializers = (serializers.toBuilder() 32 | ..addPlugin(StandardJsonPlugin()) 33 | ..add(Iso8601DateTimeSerializer()) 34 | ..add(BooleanSerializer())) 35 | .build(); 36 | -------------------------------------------------------------------------------- /lib/src/document_replication.dart: -------------------------------------------------------------------------------- 1 | library document_replication; 2 | 3 | import 'dart:convert'; 4 | 5 | import 'package:built_collection/built_collection.dart'; 6 | import 'package:built_value/built_value.dart'; 7 | import 'package:built_value/serializer.dart'; 8 | 9 | import '../couchbase_lite.dart'; 10 | import 'serializers.dart'; 11 | 12 | part 'document_replication.g.dart'; 13 | 14 | abstract class DocumentReplication 15 | implements Built { 16 | DocumentReplication._(); 17 | 18 | factory DocumentReplication( 19 | [Function(DocumentReplicationBuilder b)? updates]) = _$DocumentReplication; 20 | 21 | @BuiltValueField(serialize: false) 22 | Replicator? get replicator; 23 | @BuiltValueField(wireName: 'isPush') 24 | bool? get isPush; 25 | @BuiltValueField(wireName: 'documents') 26 | BuiltList get documents; 27 | 28 | String toJson() { 29 | return json.encode(toMap()); 30 | } 31 | 32 | Map? toMap() { 33 | return standardSerializers.serializeWith( 34 | DocumentReplication.serializer, this) as Map?; 35 | } 36 | 37 | static DocumentReplication? fromJson(String jsonString) { 38 | return fromMap(json.decode(jsonString)); 39 | } 40 | 41 | static DocumentReplication? fromMap(Map? jsonMap) { 42 | return standardSerializers.deserializeWith( 43 | DocumentReplication.serializer, jsonMap); 44 | } 45 | 46 | static Serializer get serializer => 47 | _$documentReplicationSerializer; 48 | } 49 | -------------------------------------------------------------------------------- /example/lib/models/serializers/boolean_serializer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google Inc. Please see the AUTHORS file for details. 2 | // All rights reserved. Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | import 'package:built_collection/built_collection.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | 8 | /// Alternative serializer for [DateTime]. 9 | /// 10 | /// Install this to use ISO8601 format instead of the default (microseconds 11 | /// since epoch). Use [SerializersBuilder.add] to install it. 12 | /// 13 | /// An exception will be thrown on attempt to serialize local DateTime 14 | /// instances; you must use UTC. 15 | class BooleanSerializer implements PrimitiveSerializer { 16 | final bool structured = false; 17 | @override 18 | final Iterable types = BuiltList([bool]); 19 | @override 20 | final String wireName = 'bool'; 21 | 22 | @override 23 | Object serialize(Serializers serializers, bool value, 24 | {FullType specifiedType = FullType.unspecified}) { 25 | return value; 26 | } 27 | 28 | @override 29 | bool deserialize(Serializers serializers, Object serialized, 30 | {FullType specifiedType = FullType.unspecified}) { 31 | if (serialized is bool) { 32 | return serialized; 33 | } else if (serialized is int) { 34 | if (serialized == 1) { 35 | return true; 36 | } else if (serialized == 0) { 37 | return false; 38 | } 39 | } 40 | 41 | throw ArgumentError.value( 42 | serialized, 'serialized', 'Must be true, false, 0, or 1.'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /couchbase_lite.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /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/lib/models/database/beer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | library beer; 16 | 17 | import 'dart:convert'; 18 | 19 | import 'package:built_value/built_value.dart'; 20 | import 'package:built_value/serializer.dart'; 21 | 22 | import 'serializers.dart'; 23 | 24 | part 'beer.g.dart'; 25 | 26 | abstract class Beer implements Built { 27 | Beer._(); 28 | 29 | factory Beer([updates(BeerBuilder b)?]) = _$Beer; 30 | 31 | @BuiltValueField(wireName: 'beerID') 32 | String? get beerID; 33 | @BuiltValueField(wireName: 'name') 34 | String? get name; 35 | 36 | String toJson() { 37 | return json.encode(toMap()); 38 | } 39 | 40 | Map? toMap() { 41 | return standardSerializers.serializeWith(Beer.serializer, this) as Map?; 42 | } 43 | 44 | static Beer? fromJson(String jsonString) { 45 | return fromMap(json.decode(jsonString)); 46 | } 47 | 48 | static Beer? fromMap(Map? jsonMap) { 49 | return standardSerializers.deserializeWith(Beer.serializer, jsonMap); 50 | } 51 | 52 | static Serializer get serializer => _$beerSerializer; 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/blob.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | /// Couchbase Lite blob. The Blob is immutable. 4 | class Blob { 5 | Blob.data(this._contentType, this._data); 6 | 7 | Blob._fromMap(Map map) { 8 | _contentType = map['content_type']; 9 | _digest = map['digest']; 10 | _length = map['length']; 11 | _data = map['data']; 12 | } 13 | 14 | String? _contentType; 15 | String? _digest; 16 | int? _length; 17 | Uint8List? _data; 18 | String? get contentType => _contentType; 19 | String? get digest => _digest; 20 | int? get length => _length; 21 | Uint8List? blobData; 22 | 23 | Future contentFromDatabase(Database database) async { 24 | Future readContent() async { 25 | var blobPath = database.path! + 26 | 'Attachments/' + 27 | _digest!.replaceFirst('sha1-', '').replaceAll('/', '_') + 28 | '.blob'; 29 | 30 | var file = File(blobPath); 31 | return file.existsSync() ? file.readAsBytes() : null; 32 | } 33 | 34 | blobData ??= await readContent(); 35 | return blobData; 36 | } 37 | 38 | Future get content async { 39 | // Load data here if needed 40 | _data ??= await Database._methodChannel.invokeMethod( 41 | 'getBlobContentWithDigest', {'digest': _digest}); 42 | 43 | return _data; 44 | } 45 | 46 | /// Gets content of the current object as a Dictionary. 47 | /// 48 | /// - Returns: The Dictionary representing the content of the current object. 49 | Map toMap() { 50 | return { 51 | 'content_type': _contentType, 52 | 'digest': _digest, 53 | 'length': _length, 54 | 'data': _data, 55 | '@type': 'blob' 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /example/lib/models/database/brewery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | library brewery; 16 | 17 | import 'dart:convert'; 18 | 19 | import 'package:built_value/built_value.dart'; 20 | import 'package:built_value/serializer.dart'; 21 | 22 | import 'serializers.dart'; 23 | 24 | part 'brewery.g.dart'; 25 | 26 | abstract class Brewery implements Built { 27 | Brewery._(); 28 | 29 | factory Brewery([updates(BreweryBuilder b)?]) = _$Brewery; 30 | 31 | @BuiltValueField(serialize: false) 32 | String? get id; 33 | @BuiltValueField(wireName: 'name') 34 | String? get name; 35 | 36 | String toJson() { 37 | return json.encode(toMap()); 38 | } 39 | 40 | Map? toMap() { 41 | return standardSerializers.serializeWith(Brewery.serializer, this) as Map?; 42 | } 43 | 44 | static Brewery? fromJson(String jsonString) { 45 | return fromMap(json.decode(jsonString)); 46 | } 47 | 48 | static Brewery? fromMap(Map? jsonMap) { 49 | return standardSerializers.deserializeWith(Brewery.serializer, jsonMap); 50 | } 51 | 52 | static Serializer get serializer => _$brewerySerializer; 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | couchbase_lite_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 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/couchbase_lite.dart: -------------------------------------------------------------------------------- 1 | library couchbase_lite; 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'dart:collection'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:flutter/services.dart'; 9 | import 'package:uuid/uuid.dart'; 10 | 11 | import 'src/document_replication.dart'; 12 | 13 | export 'src/document_replication.dart'; 14 | export 'src/replicated_document.dart'; 15 | 16 | part 'src/authenticator.dart'; 17 | part 'src/blob.dart'; 18 | part 'src/database.dart'; 19 | part 'src/log.dart'; 20 | part 'src/document.dart'; 21 | part 'src/fragment.dart'; 22 | part 'src/index.dart'; 23 | part 'src/listener_token.dart'; 24 | part 'src/mutable_document.dart'; 25 | part 'src/query/expression/expression.dart'; 26 | part 'src/query/expression/full_text_expression.dart'; 27 | part 'src/query/expression/meta.dart'; 28 | part 'src/query/expression/meta_expression.dart'; 29 | part 'src/query/expression/property_expression.dart'; 30 | part 'src/query/expression/variable_expression.dart'; 31 | part 'src/query/expression/array_expression.dart'; 32 | part 'src/query/expression/array_expression_in.dart'; 33 | part 'src/query/expression/array_expression_satisfies.dart'; 34 | part 'src/query/from.dart'; 35 | part 'src/query/functions.dart'; 36 | part 'src/query/array_functions.dart'; 37 | part 'src/query/group_by.dart'; 38 | part 'src/query/having.dart'; 39 | part 'src/query/join.dart'; 40 | part 'src/query/joins.dart'; 41 | part 'src/query/limit.dart'; 42 | part 'src/query/order_by.dart'; 43 | part 'src/query/ordering.dart'; 44 | part 'src/query/parameters.dart'; 45 | part 'src/query/query.dart'; 46 | part 'src/query/query_builder.dart'; 47 | part 'src/query/result.dart'; 48 | part 'src/query/result_set.dart'; 49 | part 'src/query/select.dart'; 50 | part 'src/query/select_result.dart'; 51 | part 'src/query/where.dart'; 52 | part 'src/replicator.dart'; 53 | part 'src/replicator_configuration.dart'; 54 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # couchbase_lite_example 2 | 3 | Demonstrates how to use the couchbase_lite plugin. 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 | 18 | ## Local Server Setup 19 | 20 | Download and setup Couchbase Server / Sync Gateway Community Editions on your local machine the following link 21 | - [Sync Gatway Getting Started](https://docs.couchbase.com/sync-gateway/current/getting-started.html) 22 | - [Couchbase Downloads](https://www.couchbase.com/downloads) 23 | 24 | Setup beer-sample database [Local Couchbase Server](http://127.0.0.1:8091/): 25 | 26 | - Add the beer-sample bucket: Settings > Sample Buckets 27 | - Create a sync_gateway user in the Couchbase Server under Security 28 | - Give sync_gateway access to the beer-sample 29 | 30 | Start Sync Gateway: 31 | 32 | ~/Downloads/couchbase-sync-gateway/bin/sync_gateway ~/path/to/sync-gateway-config.json 33 | 34 | *Note*: Included in this example is sync-gateway-config.json (Login credentials u: foo / p: bar) 35 | 36 | As of Android Pie, version 9, API 28, cleartext support is disabled, by default. Although wss: protocol URLs are not affected, in order to use the ws: protocol, applications must target API 27 or lower, or must configure application network security as described [here](https://developer.android.com/training/articles/security-config#CleartextTrafficPermitted). 37 | 38 | ```xml 39 | 40 | 41 | ``` -------------------------------------------------------------------------------- /ios/Classes/ReplicatorEventListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReplicatorEventListener.swift 3 | // 4 | // Created by Luca Christille on 25/10/2018. 5 | // 6 | 7 | import Foundation 8 | import CouchbaseLiteSwift 9 | 10 | class ReplicatorEventListener: FlutterStreamHandler { 11 | //weak var mCBManager : CBManager? 12 | //var mListenerToken: ListenerToken? 13 | var mEventSink: FlutterEventSink? 14 | 15 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 16 | mEventSink = events 17 | /*mListenerToken = mCBManager?.getReplicator()?.addChangeListener { [weak self] (change) in 18 | guard let events = self?.mEventSink else { 19 | return 20 | } 21 | 22 | if let error = change.status.error { 23 | events(FlutterError(code: "CouchbaseLiteException",message: "Error during replication",details: error.localizedDescription)) 24 | } else { 25 | switch (change.status.activity) { 26 | case .busy: 27 | events("BUSY") 28 | case .idle: 29 | events("IDLE") 30 | case .offline: 31 | events("OFFLINE") 32 | case .stopped: 33 | events("STOPPED") 34 | case .connecting: 35 | events("CONNECTING") 36 | } 37 | } 38 | }*/ 39 | return nil 40 | } 41 | 42 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 43 | /*guard let token = mListenerToken else { 44 | mListenerToken = nil 45 | mEventSink = nil 46 | return nil 47 | } 48 | 49 | mCBManager?.getReplicator()?.removeChangeListener(withToken: token) 50 | mListenerToken = nil*/ 51 | mEventSink = nil 52 | return nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /android/src/main/java/com/saltechsystems/couchbase_lite/ReplicationEventListener.java: -------------------------------------------------------------------------------- 1 | package com.saltechsystems.couchbase_lite; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.couchbase.lite.CouchbaseLiteException; 6 | import com.couchbase.lite.ReplicatorChange; 7 | import com.couchbase.lite.ReplicatorChangeListener; 8 | 9 | import io.flutter.plugin.common.EventChannel; 10 | 11 | public class ReplicationEventListener implements EventChannel.StreamHandler, ReplicatorChangeListener { 12 | public EventChannel.EventSink mEventSink; 13 | 14 | /* 15 | * IMPLEMENTATION OF EVENTCHANNEL.STREAMHANDLER 16 | */ 17 | @Override 18 | public void onListen(Object o, final EventChannel.EventSink eventSink) { 19 | mEventSink = eventSink; 20 | } 21 | 22 | @Override 23 | public void onCancel(Object o) { 24 | mEventSink = null; 25 | } 26 | 27 | /* 28 | * IMPLEMENTATION OF REPLICATORCHANGELISTENER INTERFACE 29 | */ 30 | 31 | @Override 32 | public void changed(@NonNull ReplicatorChange change) { 33 | if (mEventSink == null) { 34 | return; 35 | } 36 | 37 | CouchbaseLiteException error = change.getStatus().getError(); 38 | if (error != null) { 39 | mEventSink.error("CouchbaseLiteException", "Error during replication", error.getCode()); 40 | } else { 41 | switch (change.getStatus().getActivityLevel()) { 42 | case BUSY: 43 | mEventSink.success("BUSY"); 44 | break; 45 | case IDLE: 46 | mEventSink.success("IDLE"); 47 | break; 48 | case OFFLINE: 49 | mEventSink.success("OFFLINE"); 50 | break; 51 | case STOPPED: 52 | mEventSink.success("STOPPED"); 53 | break; 54 | case CONNECTING: 55 | mEventSink.success("CONNECTING"); 56 | break; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.saltechsystems.couchbase_lite_example" 37 | minSdkVersion 22 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/query/expression/array_expression_satisfies.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class ArrayExpressionSatisfies extends Object with Expression { 4 | ArrayExpressionSatisfies(this.type, this.variable, this.inExpression) { 5 | _internalExpressionStack.add({type: inExpression.internalExpressionStack}); 6 | } 7 | 8 | ArrayExpressionSatisfies._clone(ArrayExpressionSatisfies expression) 9 | : type = expression.type, 10 | this.variable = expression.variable, 11 | this.inExpression = expression.inExpression { 12 | _internalExpressionStack.add({type: inExpression.internalExpressionStack}); 13 | } 14 | 15 | final String type; 16 | final VariableExpression variable; 17 | final Expression inExpression; 18 | 19 | Expression satisfies(Expression expression) { 20 | if (expression == null) { 21 | throw Exception('expression cannot be null.'); 22 | } 23 | return QuantifiedExpression(type, variable, inExpression, expression); 24 | } 25 | 26 | @override 27 | ArrayExpressionSatisfies _clone() { 28 | return ArrayExpressionSatisfies._clone(this); 29 | } 30 | } 31 | 32 | class QuantifiedExpression extends Object with Expression { 33 | QuantifiedExpression( 34 | this.type, 35 | this.variable, 36 | this.inExpression, 37 | this.expression, 38 | ) { 39 | _internalExpressionStack.addAll(variable.internalExpressionStack); 40 | _internalExpressionStack.add({ 41 | type: inExpression.internalExpressionStack, 42 | 'satisfies': expression.internalExpressionStack, 43 | }); 44 | } 45 | 46 | QuantifiedExpression._clone(QuantifiedExpression expression) 47 | : type = expression.type, 48 | this.expression = expression, 49 | this.variable = expression.variable, 50 | this.inExpression = expression.inExpression { 51 | _internalExpressionStack..addAll(expression.internalExpressionStack); 52 | } 53 | 54 | String type; 55 | VariableExpression variable; 56 | Expression inExpression; 57 | Expression expression; 58 | 59 | @override 60 | QuantifiedExpression _clone() { 61 | return QuantifiedExpression._clone(this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/lib/models/api/token_response.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | library token_response; 16 | 17 | import 'dart:convert'; 18 | 19 | import 'package:built_value/built_value.dart'; 20 | import 'package:built_value/serializer.dart'; 21 | 22 | import 'serializers.dart'; 23 | 24 | part 'token_response.g.dart'; 25 | 26 | abstract class TokenResponse 27 | implements Built { 28 | TokenResponse._(); 29 | 30 | factory TokenResponse([updates(TokenResponseBuilder b)?]) = _$TokenResponse; 31 | 32 | @BuiltValueField(wireName: 'access_token') 33 | String? get accessToken; 34 | 35 | @BuiltValueField(wireName: 'refresh_token') 36 | String? get refreshToken; 37 | @BuiltValueField(wireName: 'token_type') 38 | String? get tokenType; 39 | @BuiltValueField(wireName: 'expires_in') 40 | int? get expiresIn; 41 | @BuiltValueField(wireName: 'scope') 42 | String? get scope; 43 | 44 | String toJson() { 45 | return json.encode(toMap()); 46 | } 47 | 48 | Map? toMap() { 49 | return standardSerializers.serializeWith(TokenResponse.serializer, this) as Map?; 50 | } 51 | 52 | static TokenResponse? fromJson(String jsonString) { 53 | return fromMap(json.decode(jsonString)); 54 | } 55 | 56 | static TokenResponse? fromMap(Map? jsonMap) { 57 | return standardSerializers.deserializeWith( 58 | TokenResponse.serializer, jsonMap); 59 | } 60 | 61 | static Serializer get serializer => _$tokenResponseSerializer; 62 | } 63 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .classpath 21 | .project 22 | .settings/ 23 | .vscode/ 24 | 25 | # packages file containing multi-root paths 26 | .packages.generated 27 | 28 | # Flutter/Dart/Pub related 29 | **/doc/api/ 30 | .dart_tool/ 31 | .flutter-plugins 32 | .flutter-plugins-dependencies 33 | **/generated_plugin_registrant.dart 34 | .packages 35 | .pub-cache/ 36 | .pub/ 37 | build/ 38 | flutter_*.png 39 | linked_*.ds 40 | unlinked.ds 41 | unlinked_spec.ds 42 | 43 | # Android related 44 | **/android/**/gradle-wrapper.jar 45 | **/android/.gradle 46 | **/android/captures/ 47 | **/android/gradlew 48 | **/android/gradlew.bat 49 | **/android/local.properties 50 | **/android/**/GeneratedPluginRegistrant.java 51 | **/android/key.properties 52 | *.jks 53 | 54 | # iOS/XCode related 55 | **/ios/**/*.mode1v3 56 | **/ios/**/*.mode2v3 57 | **/ios/**/*.moved-aside 58 | **/ios/**/*.pbxuser 59 | **/ios/**/*.perspectivev3 60 | **/ios/**/*sync/ 61 | **/ios/**/.sconsign.dblite 62 | **/ios/**/.tags* 63 | **/ios/**/.vagrant/ 64 | **/ios/**/DerivedData/ 65 | **/ios/**/Icon? 66 | **/ios/**/Podfile.lock 67 | **/ios/**/Pods/ 68 | **/ios/**/.symlinks/ 69 | **/ios/**/profile 70 | **/ios/**/xcuserdata 71 | **/ios/.generated/ 72 | **/ios/Flutter/.last_build_id 73 | **/ios/Flutter/App.framework 74 | **/ios/Flutter/Flutter.framework 75 | **/ios/Flutter/Flutter.podspec 76 | **/ios/Flutter/Generated.xcconfig 77 | **/ios/Flutter/ephemeral 78 | **/ios/Flutter/app.flx 79 | **/ios/Flutter/app.zip 80 | **/ios/Flutter/flutter_assets/ 81 | **/ios/Flutter/flutter_export_environment.sh 82 | **/ios/ServiceDefinitions.json 83 | **/ios/Runner/GeneratedPluginRegistrant.* 84 | 85 | # macOS 86 | **/macos/Flutter/GeneratedPluginRegistrant.swift 87 | 88 | # Coverage 89 | coverage/ 90 | 91 | # Symbols 92 | app.*.symbols 93 | 94 | # Exceptions to above rules. 95 | !**/ios/**/default.mode1v3 96 | !**/ios/**/default.mode2v3 97 | !**/ios/**/default.pbxuser 98 | !**/ios/**/default.perspectivev3 99 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 100 | !/dev/ci/**/Gemfile.lock -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 17 | 24 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: couchbase_lite_example 2 | description: Demonstrates how to use the couchbase_lite plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=2.12.0 <3.0.0' 7 | 8 | dependencies: 9 | # rxdart is used for the database and observable response classes 10 | rxdart: ^0.27.1 11 | http: ^0.13.3 12 | package_info: ^2.0.2 13 | url_launcher: ^6.0.9 14 | 15 | flutter: 16 | sdk: flutter 17 | 18 | # The following adds the Cupertino Icons font to your application. 19 | # Use with the CupertinoIcons class for iOS style icons. 20 | cupertino_icons: ^1.0.3 21 | 22 | dev_dependencies: 23 | build_runner: ^2.0.6 24 | built_value_generator: ^8.1.1 25 | 26 | flutter_test: 27 | sdk: flutter 28 | 29 | couchbase_lite: 30 | path: ../ 31 | 32 | # For information on the generic Dart part of this file, see the 33 | # following page: https://www.dartlang.org/tools/pub/pubspec 34 | 35 | # The following section is specific to Flutter. 36 | flutter: 37 | 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | 43 | # To add assets to your application, add an assets section, like this: 44 | # assets: 45 | # - images/a_dot_burr.jpeg 46 | # - images/a_dot_ham.jpeg 47 | 48 | # An image asset can refer to one or more resolution-specific "variants", see 49 | # https://flutter.dev/assets-and-images/#resolution-aware. 50 | 51 | # For details regarding adding assets from package dependencies, see 52 | # https://flutter.dev/assets-and-images/#from-packages 53 | 54 | # To add custom fonts to your application, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts from package dependencies, 72 | # see https://flutter.dev/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: couchbase_lite 2 | description: Flutter plugin for Couchbase Lite Community Edition, an embedded lightweight, noSQL database with live synchronization and offline support on Android and iOS. 3 | version: 3.0.0-nullsafety.4 4 | homepage: https://github.com/SaltechSystems/couchbase_lite 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | # Flutter versions prior to 1.12 did not support the 9 | # flutter.plugin.platforms map. 10 | flutter: ">=1.12.0" 11 | 12 | dependencies: 13 | built_collection: '>=2.0.0 <6.0.0' 14 | built_value: '>=5.5.5 <9.0.0' 15 | uuid: '>=2.0.4 <4.0.0' 16 | meta: '>=1.1.6 <2.0.0' 17 | 18 | flutter: 19 | sdk: flutter 20 | dev_dependencies: 21 | build_runner: ^2.0.6 22 | built_value_generator: ^8.0.4 23 | pedantic: ^1.11.0 24 | 25 | flutter_test: 26 | sdk: flutter 27 | 28 | # For information on the generic Dart part of this file, see the 29 | # following page: https://www.dartlang.org/tools/pub/pubspec 30 | 31 | # The following section is specific to Flutter. 32 | flutter: 33 | # This section identifies this Flutter project as a plugin project. 34 | # The androidPackage and pluginClass identifiers should not ordinarily 35 | # be modified. They are used by the tooling to maintain consistency when 36 | # adding or updating assets for this project. 37 | plugin: 38 | platforms: 39 | android: 40 | package: com.saltechsystems.couchbase_lite 41 | pluginClass: CouchbaseLitePlugin 42 | ios: 43 | pluginClass: CouchbaseLitePlugin 44 | 45 | # To add assets to your plugin package, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | # 50 | # For details regarding assets in packages, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | # 53 | # An image asset can refer to one or more resolution-specific "variants", see 54 | # https://flutter.dev/assets-and-images/#resolution-aware. 55 | 56 | # To add custom fonts to your plugin package, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts in packages, see 74 | # https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /lib/src/index.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | abstract class Index { 4 | List> toJson(); 5 | } 6 | 7 | class ValueIndexItem { 8 | ValueIndexItem(this._map); 9 | 10 | /// Creates a ValueIndexItem for the given [property] 11 | factory ValueIndexItem.property(String property) { 12 | return ValueIndexItem({'property': property}); 13 | } 14 | 15 | /// Creates a ValueIndexItem for the given [expression] 16 | factory ValueIndexItem.expression(Expression expression) { 17 | return ValueIndexItem({'expression': expression.toJson()}); 18 | } 19 | 20 | Map _map; 21 | 22 | /// Returns the json representation of this object 23 | Map toJson() => _map; 24 | } 25 | 26 | class ValueIndex extends Index { 27 | ValueIndex(this._valueIndexItems); 28 | 29 | final List _valueIndexItems; 30 | 31 | @override 32 | List> toJson() { 33 | var map = >[]; 34 | for (var item in _valueIndexItems) { 35 | map.add(item.toJson()); 36 | } 37 | return map; 38 | } 39 | } 40 | 41 | class FullTextIndexItem { 42 | FullTextIndexItem(this._map); 43 | 44 | /// Creates a FullTextIndexItem for the given [property] 45 | factory FullTextIndexItem.property(String property) { 46 | return FullTextIndexItem({'property': property}); 47 | } 48 | 49 | Map _map; 50 | 51 | /// Returns the json representation of this object 52 | Map toJson() => _map; 53 | } 54 | 55 | class FullTextIndex extends Index { 56 | FullTextIndex(this._fullTextIndexItems); 57 | 58 | final List _fullTextIndexItems; 59 | bool? _ignoreAccents; 60 | String? _language; 61 | 62 | FullTextIndex ignoreAccents(bool ignoreAccents) { 63 | _ignoreAccents = ignoreAccents; 64 | return this; 65 | } 66 | 67 | FullTextIndex language(String language) { 68 | _language = language; 69 | return this; 70 | } 71 | 72 | @override 73 | List> toJson() { 74 | var map = >[]; 75 | for (var item in _fullTextIndexItems) { 76 | map.add(item.toJson()); 77 | } 78 | if (_ignoreAccents != null) { 79 | map.add({'ignoreAccents': _ignoreAccents}); 80 | } 81 | if (_language != null) { 82 | map.add({'language': _language}); 83 | } 84 | return map; 85 | } 86 | } 87 | 88 | class IndexBuilder { 89 | /// Creates a value index with the given index items. The index items are a list of the properties or expressions to be indexed. 90 | static ValueIndex valueIndex({required List items}) { 91 | return ValueIndex(items); 92 | } 93 | 94 | /// Creates a full-text index with the given index items. The index items are a list of the properties to be indexed. 95 | static FullTextIndex fullTextIndex({required List items}) { 96 | return FullTextIndex(items); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /example/lib/beer_sample_app.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:couchbase_lite_example/pages/home_page.dart'; 16 | import 'package:flutter/material.dart'; 17 | import 'colors.dart'; 18 | 19 | import 'package:couchbase_lite_example/data/api_provider.dart'; 20 | import 'package:couchbase_lite_example/data/repository.dart'; 21 | import 'package:couchbase_lite_example/pages/login.dart'; 22 | 23 | enum AppMode { production, development } 24 | 25 | class BeerSampleApp extends StatefulWidget { 26 | BeerSampleApp(this.mode); 27 | 28 | final AppMode mode; 29 | 30 | @override 31 | _BeerSampleAppState createState() => _BeerSampleAppState(); 32 | } 33 | 34 | class _BeerSampleAppState extends State { 35 | @override 36 | void initState() { 37 | super.initState(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return MaterialApp( 43 | title: 'Werk Sheets', 44 | theme: _kTheme, 45 | routes: { 46 | '/home': (BuildContext context) => HomePage(), 47 | '/': (BuildContext context) => LoginPage(widget.mode), 48 | }, 49 | ); 50 | } 51 | 52 | bool isActive() => mounted; 53 | 54 | @override 55 | void dispose() { 56 | ApiProvider.instance.client.close(); 57 | Repository.instance.dispose(); 58 | super.dispose(); 59 | } 60 | } 61 | 62 | final ThemeData _kTheme = _buildTheme(); 63 | 64 | ThemeData _buildTheme() { 65 | final ThemeData base = ThemeData( 66 | // Define the default brightness and colors. 67 | brightness: Brightness.light, 68 | primaryColor: kPrimaryColor, 69 | accentColor: kAccentColor, 70 | 71 | // Define the default font family. 72 | fontFamily: 'Rubik'); 73 | 74 | return base.copyWith( 75 | buttonTheme: base.buttonTheme.copyWith( 76 | textTheme: ButtonTextTheme.primary, 77 | shape: RoundedRectangleBorder( 78 | borderRadius: BorderRadius.all(Radius.circular(4.0)), 79 | ), 80 | ), 81 | inputDecorationTheme: InputDecorationTheme( 82 | border: OutlineInputBorder( 83 | borderRadius: BorderRadius.all(Radius.circular(4.0)), 84 | ), 85 | ), 86 | textTheme: _buildTextTheme(base.textTheme), 87 | //primaryTextTheme: _buildTextTheme(base.primaryTextTheme), 88 | accentTextTheme: _buildTextTheme(base.accentTextTheme), 89 | ); 90 | } 91 | 92 | TextTheme _buildTextTheme(TextTheme base) { 93 | return base.apply( 94 | displayColor: kPrimaryColor, 95 | bodyColor: kPrimaryColor, 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/replicator_configuration.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | enum ReplicatorType { pushAndPull, push, pull } 4 | 5 | class ReplicatorConfiguration { 6 | ReplicatorConfiguration(this.database, this.target); 7 | 8 | final Database database; 9 | final String target; 10 | ReplicatorType replicatorType = ReplicatorType.pushAndPull; 11 | bool? continuous; 12 | String? pinnedServerCertificate; 13 | Authenticator? authenticator; 14 | List? channels; 15 | /// Filters which documents should be replicated. Keys are attribute names, 16 | /// and values are a list of allowed values for that attribute. A document 17 | /// will only be pushed if it matches all of the filters in this map. 18 | Map>? pushAttributeFilters; 19 | @Deprecated('use pushAttributeFilters instead for multiple filter support') 20 | List? pushAttributeValuesFilter; 21 | @Deprecated('use pushAttributeFilters instead for multiple filter support') 22 | String? pushAttributeKeyFilter; 23 | /// Filters which documents should be replicated. Keys are attribute names, 24 | /// and values are a list of allowed values for that attribute. A document 25 | /// will only be pulled if it matches all of the filters in this map. 26 | Map>? pullAttributeFilters; 27 | @Deprecated('use pullAttributeFilters instead for multiple filter support') 28 | List? pullAttributeValuesFilter; 29 | @Deprecated('use pullAttributeFilters instead for multiple filter support') 30 | String? pullAttributeKeyFilter; 31 | Map? headers; 32 | 33 | Map toJson() { 34 | var map = {'database': database.name, 'target': target}; 35 | 36 | switch (replicatorType) { 37 | case ReplicatorType.pushAndPull: 38 | map['replicatorType'] = 'PUSH_AND_PULL'; 39 | break; 40 | case ReplicatorType.push: 41 | map['replicatorType'] = 'PUSH'; 42 | break; 43 | case ReplicatorType.pull: 44 | map['replicatorType'] = 'PULL'; 45 | break; 46 | } 47 | 48 | if (pinnedServerCertificate != null) { 49 | map['pinnedServerCertificate'] = pinnedServerCertificate; 50 | } 51 | 52 | if (authenticator != null) { 53 | map['authenticator'] = authenticator; 54 | } 55 | 56 | if (continuous != null) { 57 | map['continuous'] = continuous; 58 | } 59 | 60 | if (channels != null) { 61 | map['channels'] = channels; 62 | } 63 | 64 | if (pushAttributeKeyFilter != null && pushAttributeValuesFilter != null) { 65 | pushAttributeFilters ??= {}; 66 | pushAttributeFilters![pushAttributeKeyFilter!] = pushAttributeValuesFilter!; 67 | } 68 | 69 | if (pushAttributeFilters != null) { 70 | map['pushAttributeFilters'] = pushAttributeFilters; 71 | } 72 | 73 | if (pullAttributeKeyFilter != null && pullAttributeValuesFilter != null) { 74 | pullAttributeFilters ??= {}; 75 | pullAttributeFilters![pullAttributeKeyFilter!] = pullAttributeValuesFilter!; 76 | } 77 | 78 | if (pullAttributeFilters != null) { 79 | map['pullAttributeFilters'] = pullAttributeFilters; 80 | } 81 | 82 | if (headers != null) { 83 | map['headers'] = headers; 84 | } 85 | 86 | return map; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/query/result.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Result { 4 | final Map _internalMap = {}; 5 | final List _internalList = []; 6 | final List _keys = []; 7 | 8 | bool contains(String key) { 9 | return _internalMap.containsKey(key); 10 | } 11 | 12 | int count() { 13 | return _internalList.length; 14 | } 15 | 16 | List? getList({int? index, String? key}) { 17 | var result = getValue(index: index, key: key); 18 | if (result is List) { 19 | return result; 20 | } else { 21 | return null; 22 | } 23 | } 24 | 25 | Blob? getBlob({int? index, String? key}) { 26 | var result = getValue(index: index, key: key); 27 | if (result is Map && result['@type'] == 'blob') { 28 | return Blob._fromMap(result); 29 | } else { 30 | return null; 31 | } 32 | } 33 | 34 | bool? getBoolean({int? index, String? key}) { 35 | var result = getValue(index: index, key: key); 36 | if (result is bool) { 37 | return result; 38 | } else if (result is num) { 39 | return result != 0; 40 | } else { 41 | return null; 42 | } 43 | } 44 | 45 | //TODO: implement Date object and getDate 46 | 47 | double? getDouble({int? index, String? key}) { 48 | var result = getValue(index: index, key: key); 49 | if (result is double) { 50 | return result; 51 | } else { 52 | return null; 53 | } 54 | } 55 | 56 | int? getInt({int? index, String? key}) { 57 | var result = getValue(index: index, key: key); 58 | if (result is int) { 59 | return result; 60 | } else { 61 | return null; 62 | } 63 | } 64 | 65 | List getKeys() { 66 | return _keys; 67 | } 68 | 69 | String? getString({int? index, String? key}) { 70 | var result = getValue(index: index, key: key); 71 | if (result is String) { 72 | return result; 73 | } else { 74 | return null; 75 | } 76 | } 77 | 78 | Object? getValue({int? index, String? key}) { 79 | if (null != index && _internalList.length > index) { 80 | return _internalList[index]; 81 | } else if (null != key && _internalMap.containsKey(key)) { 82 | return _internalMap[key]; 83 | } else { 84 | return null; 85 | } 86 | } 87 | 88 | //TODO: implement iterator() 89 | 90 | List toList() { 91 | return _internalList; 92 | } 93 | 94 | Map toMap() { 95 | return _internalMap; 96 | } 97 | 98 | Fragment operator [](dynamic key) { 99 | if (key is int) { 100 | if (key < _internalList.length) { 101 | return Fragment._init(_internalList[key]); 102 | } 103 | } else if (key is String) { 104 | return Fragment._init(_internalMap[key]); 105 | } 106 | 107 | return Fragment._init(null); 108 | } 109 | 110 | void setMap(Map map) { 111 | _internalMap.clear(); 112 | _internalMap.addAll(map); 113 | } 114 | 115 | void setList(List list) { 116 | _internalList.clear(); 117 | _internalList.addAll(list); 118 | } 119 | 120 | void setKeys(List keys) { 121 | _keys.clear(); 122 | _keys.addAll(keys); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at weltbr01@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /example/lib/data/observable_response.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | 17 | import 'package:flutter/foundation.dart'; 18 | import 'package:rxdart/rxdart.dart'; 19 | 20 | // We will use observable responses to listen to changes in a query and 21 | // propagate the changes back to the caller through the stream 22 | class ObservableResponse implements StreamController { 23 | ObservableResponse(this._result, [this._onDispose]); 24 | 25 | final Subject _result; 26 | final VoidCallback? _onDispose; 27 | 28 | @override 29 | void add(data) => _result.add(data); 30 | 31 | @override 32 | ControllerCallback get onCancel => throw UnsupportedError( 33 | 'ObservableResponses do not support cancel callbacks'); 34 | 35 | @override 36 | ControllerCallback get onListen => throw UnsupportedError( 37 | 'ObservableResponses do not support listen callbacks'); 38 | 39 | @override 40 | ControllerCallback get onPause => throw UnsupportedError( 41 | 'ObservableResponses do not support pause callbacks'); 42 | 43 | @override 44 | ControllerCallback get onResume => throw UnsupportedError( 45 | 'ObservableResponses do not support resume callbacks'); 46 | 47 | @override 48 | void addError(Object error, [StackTrace? stackTrace]) => 49 | throw UnsupportedError( 50 | 'ObservableResponses do not support adding errors'); 51 | 52 | @override 53 | Future addStream(Stream source, {bool? cancelOnError}) => 54 | throw UnsupportedError( 55 | 'ObservableResponses do not support adding streams'); 56 | 57 | @override 58 | Future get done => _result.done.then((value) => value as bool); 59 | 60 | @override 61 | bool get hasListener => _result.hasListener; 62 | 63 | @override 64 | bool get isClosed => _result.isClosed; 65 | 66 | @override 67 | bool get isPaused => _result.isPaused; 68 | 69 | @override 70 | StreamSink get sink => _result.sink; 71 | 72 | @override 73 | Stream get stream => _result.stream; 74 | 75 | @override 76 | Future close() { 77 | if (_onDispose != null) { 78 | // Do operations here like closing streams and removing listeners 79 | _onDispose!(); 80 | } 81 | 82 | return _result.close(); 83 | } 84 | 85 | @override 86 | set onCancel(Function()? onCancelHandler) => throw UnsupportedError( 87 | 'ObservableResponses do not support cancel callbacks'); 88 | 89 | @override 90 | set onListen(void Function()? onListenHandler) => throw UnsupportedError( 91 | 'ObservableResponses do not support listen callbacks'); 92 | 93 | @override 94 | set onPause(void Function()? onPauseHandler) => throw UnsupportedError( 95 | 'ObservableResponses do not support pause callbacks'); 96 | 97 | @override 98 | set onResume(void Function()? onResumeHandler) => throw UnsupportedError( 99 | 'ObservableResponses do not support resume callbacks'); 100 | } 101 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Classes/DataConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataConverter.swift 3 | // 4 | // Created by Saltech Systems on 5/10/19. 5 | // 6 | 7 | import Foundation 8 | import CouchbaseLiteSwift 9 | import Flutter 10 | 11 | extension CBManager { 12 | static func convertGETValue(_ value: Any?) -> Any? { 13 | switch (value) { 14 | case let blob as Blob: 15 | if let digest = blob.digest { 16 | // Store the blob for retrieving the content 17 | setBlobWithDigest(digest, blob: blob) 18 | } 19 | 20 | // Don't return the data, JSONMessageCodec doesn't support it 21 | return [ 22 | "content_type": blob.contentType as Any, 23 | "digest": blob.digest as Any, 24 | "length": blob.length, 25 | "@type": "blob" 26 | ] 27 | case let dict as DictionaryObject: 28 | return convertGETDictionary(dict) 29 | case let array as ArrayObject: 30 | return convertGETArray(array) 31 | default: 32 | return value 33 | } 34 | } 35 | 36 | static func convertGETDictionary(_ dict: DictionaryObject) -> [String: Any] { 37 | var rtnMap: [String: Any] = [:] 38 | for key in dict.keys { 39 | rtnMap[key] = convertGETValue(dict[key].value) 40 | } 41 | 42 | return rtnMap 43 | } 44 | 45 | static func convertGETArray(_ array: ArrayObject) -> [Any?] { 46 | var rtnList: [Any?] = []; 47 | for idx in 0.. Any? { 54 | switch value { 55 | case let dict as Dictionary: 56 | let result = convertSETDictionary(dict) 57 | 58 | guard let type = result?["@type"] as? String, type == "blob" else { 59 | return result 60 | } 61 | 62 | if let _ = result?["digest"] as? String { 63 | // Preserve the map value 64 | return result 65 | } 66 | 67 | guard let contentType = result?["content_type"] as? String, let data = result?["data"] as? Data else { 68 | // Preserve the map value 69 | return result 70 | } 71 | 72 | // Create a new blob 73 | return Blob(contentType: contentType, data: data) 74 | case let array as Array: 75 | return convertSETArray(array) 76 | case let bool as NSNumber: 77 | if (bool === kCFBooleanTrue || bool === kCFBooleanFalse) { 78 | return bool === kCFBooleanTrue 79 | } else { 80 | return value 81 | } 82 | case let flutterData as FlutterStandardTypedData: 83 | return flutterData.data 84 | default: 85 | return value 86 | } 87 | } 88 | 89 | static func convertSETDictionary(_ dictionary: [String: Any]?) -> [String: Any]? { 90 | guard let dict = dictionary else { 91 | return nil 92 | } 93 | 94 | var result: [String: Any] = [:] 95 | for (key, value) in dict { 96 | result[key] = convertSETValue(value) 97 | } 98 | 99 | return result 100 | } 101 | 102 | static func convertSETArray(_ array: [Any?]?) -> [Any?]? { 103 | guard let a = array else { 104 | return nil 105 | } 106 | 107 | var result: [Any?] = []; 108 | for v in a { 109 | result.append(convertSETValue(v)) 110 | } 111 | return result 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.0-nullsafety.4 2 | 3 | * Fixed issue with long value in query 4 | * Fixed building issue with Swift 5.5.1 compiler 5 | 6 | ## 3.0.0-nullsafety.3 7 | 8 | * Added saveDocuments and deleteDocuments 9 | 10 | ## 3.0.0-nullsafety.2 11 | 12 | * Update Android library to version 2.8.6 13 | * Migrate the example app to null safety 14 | * Fix a down cast issue 15 | 16 | ## 3.0.0-nullsafety.1 17 | 18 | * Added setting logging level feature 19 | 20 | ## 3.0.0-nullsafety.0 21 | 22 | * Migrate to NNDB 23 | * Update libraries to version 2.8.4 (iOS) & 2.8.5 (Android) 24 | * Updated gradle to 4.0.2 25 | 26 | ## 2.8.1 27 | 28 | * Update libraries to version 2.8.1 29 | * Updated gradle to 3.5.4 and compile version to 29 30 | 31 | ## 2.7.1+7 32 | 33 | * Read blob files without platform code 34 | * Added FullTextIndex and FullTextExpressions 35 | * Support filtering replication on multiple attributes 36 | * Fix with Result.getBoolean 37 | * Fixed error when loading blobs 38 | * Removed caching from blobs 39 | 40 | ## 2.7.1+6 41 | 42 | * Added channels to replicator configs 43 | * Added headers to replicator config 44 | * Added push filters to replicator config 45 | * Added pull filters to replicator config 46 | 47 | ## 2.7.1+5 48 | 49 | * Added deletion of indexes 50 | * Fixed issues with blobs and queries 51 | * Added Fragments to expose value getters for Documents 52 | * Added [] operators to Result, Document, and MutableDocument classes to retrieve Fragments 53 | 54 | ## 2.7.1+4 55 | 56 | * Added Database addDocumentChangeListener 57 | * Added Database addChangeListener 58 | * Added Database removeChangeListener 59 | 60 | ## 2.7.1+3 61 | 62 | * Setting up plugin CI/CD 63 | 64 | ## 2.7.1+2 65 | 66 | * Added indexes 67 | * Added explain to queries 68 | 69 | ## 2.7.1+1 70 | 71 | * Fixed issue with optional Session cookieName 72 | 73 | ## 2.7.1 74 | 75 | * Update libraries to version 2.7.1 76 | * Fixed example for logging out 77 | * Updated console logging to Debug for debug mode / Error for everything else 78 | * Removed file logging 79 | 80 | ## 2.7.0+3 81 | 82 | * Giving a simplified example and advanced example 83 | 84 | ## 2.7.0+2 85 | 86 | * Updated documentation 87 | 88 | ## 2.7.0+1 89 | 90 | * Updated Examples to use bloc pattern 91 | 92 | ## 2.7.0 93 | 94 | * Updated Coubchbase Lite libraries to version 2.7.0 95 | * Migrated to Android X 96 | * Added concurrency control 97 | 98 | # 2.5.1+8 99 | 100 | * Fixed a bug on Android which results in an error for not posting results on the UI Thread 101 | 102 | # 2.5.1+7 103 | 104 | * Deprecated Database saveDocument methods and replaced with save taking a MutableDocument as an argument 105 | * Fixed a concurrent modification exception with query listeners 106 | 107 | # 2.5.1+6 108 | 109 | * Fixed an issue where Query Listeners were not being released 110 | * Fixed an issue where the database reference was not being released on close for Android 111 | 112 | # 2.5.1+5 113 | 114 | * Fixed Replicator Configuration bug which required certain variables like Pinned Certificate to not receive an Platform Error 115 | * Changed the Map object in Document from unmodifiable to a modifiable copy of the Map object 116 | * Renamed the functions In,Is,As to in,iS,aS to comply with flutter plugin standards 117 | * Added a destroy method to Replicator for cleaning up variable references and listeners 118 | 119 | ## 2.5.1+4 120 | 121 | * Updated documentation 122 | * Added more test cases 123 | 124 | ## 2.5.1+3 125 | 126 | * Fixed issue with Replication EventChannel 127 | * Added Travis CI and Code Coverage with Coveralls 128 | * Added support for the having clause 129 | * Fixed some issues with Queries 130 | 131 | ## 2.5.1+2 132 | 133 | * Added all classes as part of couchbase_lite library to eliminate the need to import every class individually 134 | 135 | ## 2.5.1+1 136 | 137 | * Changed return type of class Database documentWithId from Map to Document 138 | * Populated doc/api using dartdoc 139 | * Fixed some format issues 140 | 141 | ## 2.5.1 142 | 143 | * Updated documentation 144 | 145 | ## 2.5.0 146 | 147 | * Initial Release for the plugin using Couchbase Mobile 2.5 -------------------------------------------------------------------------------- /example/lib/models/database/brewery.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of brewery; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializer _$brewerySerializer = new _$BrewerySerializer(); 10 | 11 | class _$BrewerySerializer implements StructuredSerializer { 12 | @override 13 | final Iterable types = const [Brewery, _$Brewery]; 14 | @override 15 | final String wireName = 'Brewery'; 16 | 17 | @override 18 | Iterable serialize(Serializers serializers, Brewery object, 19 | {FullType specifiedType = FullType.unspecified}) { 20 | final result = []; 21 | Object? value; 22 | value = object.name; 23 | if (value != null) { 24 | result 25 | ..add('name') 26 | ..add(serializers.serialize(value, 27 | specifiedType: const FullType(String))); 28 | } 29 | return result; 30 | } 31 | 32 | @override 33 | Brewery deserialize(Serializers serializers, Iterable serialized, 34 | {FullType specifiedType = FullType.unspecified}) { 35 | final result = new BreweryBuilder(); 36 | 37 | final iterator = serialized.iterator; 38 | while (iterator.moveNext()) { 39 | final key = iterator.current as String; 40 | iterator.moveNext(); 41 | final Object? value = iterator.current; 42 | switch (key) { 43 | case 'name': 44 | result.name = serializers.deserialize(value, 45 | specifiedType: const FullType(String)) as String?; 46 | break; 47 | } 48 | } 49 | 50 | return result.build(); 51 | } 52 | } 53 | 54 | class _$Brewery extends Brewery { 55 | @override 56 | final String? id; 57 | @override 58 | final String? name; 59 | 60 | factory _$Brewery([void Function(BreweryBuilder)? updates]) => 61 | (new BreweryBuilder()..update(updates)).build(); 62 | 63 | _$Brewery._({this.id, this.name}) : super._(); 64 | 65 | @override 66 | Brewery rebuild(void Function(BreweryBuilder) updates) => 67 | (toBuilder()..update(updates)).build(); 68 | 69 | @override 70 | BreweryBuilder toBuilder() => new BreweryBuilder()..replace(this); 71 | 72 | @override 73 | bool operator ==(Object other) { 74 | if (identical(other, this)) return true; 75 | return other is Brewery && id == other.id && name == other.name; 76 | } 77 | 78 | @override 79 | int get hashCode { 80 | return $jf($jc($jc(0, id.hashCode), name.hashCode)); 81 | } 82 | 83 | @override 84 | String toString() { 85 | return (newBuiltValueToStringHelper('Brewery') 86 | ..add('id', id) 87 | ..add('name', name)) 88 | .toString(); 89 | } 90 | } 91 | 92 | class BreweryBuilder implements Builder { 93 | _$Brewery? _$v; 94 | 95 | String? _id; 96 | String? get id => _$this._id; 97 | set id(String? id) => _$this._id = id; 98 | 99 | String? _name; 100 | String? get name => _$this._name; 101 | set name(String? name) => _$this._name = name; 102 | 103 | BreweryBuilder(); 104 | 105 | BreweryBuilder get _$this { 106 | final $v = _$v; 107 | if ($v != null) { 108 | _id = $v.id; 109 | _name = $v.name; 110 | _$v = null; 111 | } 112 | return this; 113 | } 114 | 115 | @override 116 | void replace(Brewery other) { 117 | ArgumentError.checkNotNull(other, 'other'); 118 | _$v = other as _$Brewery; 119 | } 120 | 121 | @override 122 | void update(void Function(BreweryBuilder)? updates) { 123 | if (updates != null) updates(this); 124 | } 125 | 126 | @override 127 | _$Brewery build() { 128 | final _$result = _$v ?? new _$Brewery._(id: id, name: name); 129 | replace(_$result); 130 | return _$result; 131 | } 132 | } 133 | 134 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new 135 | -------------------------------------------------------------------------------- /example/lib/data/api_provider.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/foundation.dart'; 16 | import '../models/api/token_response.dart'; 17 | import 'package:http/http.dart' as http; 18 | import 'repository.dart'; 19 | 20 | class ApiProvider { 21 | ApiProvider(this.client); 22 | 23 | static final ApiProvider instance = ApiProvider(http.Client()); 24 | 25 | final http.Client client; 26 | final String devUrl = "https://dev.example.com"; 27 | final prodUrl = "https://www.example.com"; 28 | 29 | Uri get authEndpoint { 30 | return Uri.parse(environment == Environment.development 31 | ? '$devUrl/oauth/v2/token' 32 | : '$prodUrl/oauth/v2/token'); 33 | } 34 | 35 | @visibleForTesting 36 | String get apiEndpoint { 37 | return environment == Environment.development 38 | ? '$devUrl/api/v1' 39 | : '$prodUrl/api/v1'; 40 | } 41 | 42 | @visibleForTesting 43 | String get clientID { 44 | return environment == Environment.development 45 | ? 'DEVELOPMENT_CLIENT_ID_GOES_HERE' 46 | : 'PRODUCTION_CLIENT_ID_GOES_HERE'; 47 | } 48 | 49 | @visibleForTesting 50 | String get clientSecret { 51 | return environment == Environment.development 52 | ? 'DEVELOPMENT_CLIENT_SECRET_GOES_HERE' 53 | : 'PRODUCTION_CLIENT_ID_GOES_HERE'; 54 | } 55 | 56 | // If refreshing the access token fails we will use this function to logout 57 | LogoutCallback? _onLogout; 58 | @visibleForTesting 59 | String? refreshToken = ""; 60 | @visibleForTesting 61 | String? accessToken = ""; 62 | @visibleForTesting 63 | String? tokenType = ""; 64 | DateTime expiresAt = DateTime(0); 65 | @visibleForTesting 66 | Environment environment = Environment.production; 67 | 68 | Future login(String username, String password, 69 | {LogoutCallback? onLogout}) async { 70 | _onLogout = onLogout; 71 | 72 | /*final response = await client.post(authEndpoint, body: { 73 | "grant_type": "password", 74 | "username": username, 75 | "password": password, 76 | "client_id": clientID, 77 | "client_secret": clientSecret 78 | });*/ 79 | 80 | final tokenPreResponse = TokenResponse((b) => b 81 | ..accessToken = "accessToken" 82 | ..tokenType = "bearer" 83 | ..refreshToken = "refreshToken" 84 | ..expiresIn = 3600 85 | ..scope = ""); 86 | 87 | final response = http.Response(tokenPreResponse.toJson(), 200); 88 | 89 | if (response.statusCode == 200) { 90 | var tokenResponse = TokenResponse.fromJson(response.body)!; 91 | accessToken = tokenResponse.accessToken; 92 | tokenType = tokenResponse.tokenType; 93 | refreshToken = tokenResponse.refreshToken; 94 | expiresAt = 95 | DateTime.now().add(Duration(seconds: tokenResponse.expiresIn!)); 96 | } 97 | 98 | return response; 99 | } 100 | 101 | @visibleForTesting 102 | Future refreshAccessTokenIfNeeded() async { 103 | if (expiresAt.add(Duration(seconds: 1)).compareTo(DateTime.now()) < 0) { 104 | final response = await client.post(authEndpoint, body: { 105 | "grant_type": "refresh_token", 106 | "client_id": clientID, 107 | "refresh_token": refreshToken 108 | }); 109 | 110 | if (response.statusCode == 200) { 111 | var tokenResponse = TokenResponse.fromJson(response.body)!; 112 | accessToken = tokenResponse.accessToken; 113 | tokenType = tokenResponse.tokenType; 114 | expiresAt = 115 | DateTime.now().add(Duration(seconds: tokenResponse.expiresIn!)); 116 | } else if (response.statusCode == 401 && _onLogout != null) { 117 | _onLogout!(LogoutMethod.apiCredentialsError); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /example/test/http_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:convert'; 16 | 17 | import 'package:http/http.dart' as http; 18 | import 'package:http_parser/http_parser.dart'; 19 | import 'package:flutter_test/flutter_test.dart'; 20 | 21 | /// A dummy URL for constructing requests that won't be sent. 22 | Uri get dummyUrl => Uri.parse('http://dartlang.org/'); 23 | 24 | /// Removes eight spaces of leading indentation from a multiline string. 25 | /// 26 | /// Note that this is very sensitive to how the literals are styled. They should 27 | /// be: 28 | /// ''' 29 | /// Text starts on own line. Lines up with subsequent lines. 30 | /// Lines are indented exactly 8 characters from the left margin. 31 | /// Close is on the same line.''' 32 | /// 33 | /// This does nothing if text is only a single line. 34 | // TODO(nweiz): Make this auto-detect the indentation level from the first 35 | // non-whitespace line. 36 | String cleanUpLiteral(String text) { 37 | var lines = text.split('\n'); 38 | if (lines.length <= 1) return text; 39 | 40 | for (var j = 0; j < lines.length; j++) { 41 | if (lines[j].length > 8) { 42 | lines[j] = lines[j].substring(8, lines[j].length); 43 | } else { 44 | lines[j] = ''; 45 | } 46 | } 47 | 48 | return lines.join('\n'); 49 | } 50 | 51 | /// A matcher that matches JSON that parses to a value that matches the inner 52 | /// matcher. 53 | Matcher parse(matcher) => _Parse(matcher); 54 | 55 | class _Parse extends Matcher { 56 | _Parse(this._matcher); 57 | 58 | final Matcher _matcher; 59 | 60 | @override 61 | bool matches(item, Map matchState) { 62 | if (item is! String) return false; 63 | 64 | dynamic parsed; 65 | try { 66 | parsed = json.decode(item); 67 | } catch (e) { 68 | return false; 69 | } 70 | 71 | return _matcher.matches(parsed, matchState); 72 | } 73 | 74 | @override 75 | Description describe(Description description) { 76 | return description 77 | .add('parses to a value that ') 78 | .addDescriptionOf(_matcher); 79 | } 80 | } 81 | 82 | /// A matcher that validates the body of a multipart request after finalization. 83 | /// 84 | /// The string "{{boundary}}" in [pattern] will be replaced by the boundary 85 | /// string for the request, and LF newlines will be replaced with CRLF. 86 | /// Indentation will be normalized. 87 | Matcher bodyMatches(String pattern) => _BodyMatches(pattern); 88 | 89 | class _BodyMatches extends Matcher { 90 | _BodyMatches(this._pattern); 91 | 92 | final String _pattern; 93 | 94 | @override 95 | bool matches(item, Map matchState) { 96 | if (item is! http.MultipartRequest) return false; 97 | 98 | return completes.matches(_checks(item), matchState); 99 | } 100 | 101 | Future _checks(http.MultipartRequest item) async { 102 | var bodyBytes = await item.finalize().toBytes(); 103 | var body = utf8.decode(bodyBytes); 104 | var contentType = MediaType.parse(item.headers['content-type']!); 105 | var boundary = contentType.parameters['boundary']!; 106 | var expected = cleanUpLiteral(_pattern) 107 | .replaceAll('\n', '\r\n') 108 | .replaceAll('{{boundary}}', boundary); 109 | 110 | expect(body, equals(expected)); 111 | expect(item.contentLength, equals(bodyBytes.length)); 112 | } 113 | 114 | @override 115 | Description describe(Description description) { 116 | return description.add('has a body that matches "$_pattern"'); 117 | } 118 | } 119 | 120 | /// A matcher that matches function or future that throws a 121 | /// [http.ClientException] with the given [message]. 122 | /// 123 | /// [message] can be a String or a [Matcher]. 124 | Matcher throwsClientException(message) => throwsA( 125 | isA().having((e) => e.message, 'message', message)); 126 | -------------------------------------------------------------------------------- /example/lib/data/repository.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | 17 | import 'package:built_collection/src/list.dart'; 18 | import 'package:couchbase_lite_example/data/observable_response.dart'; 19 | import 'package:couchbase_lite_example/models/database/beer.dart'; 20 | import 'package:flutter/foundation.dart'; 21 | import 'package:flutter/material.dart'; 22 | import 'package:rxdart/rxdart.dart'; 23 | 24 | import 'package:couchbase_lite_example/data/database.dart'; 25 | import 'package:couchbase_lite_example/data/repository.dart'; 26 | import 'package:couchbase_lite_example/data/api_provider.dart'; 27 | 28 | export 'database.dart'; 29 | 30 | enum Environment { development, production } 31 | enum LoginResult { unauthorized, authorized, disconnected, error } 32 | enum LogoutMethod { 33 | normal, 34 | apiCredentialsError, 35 | dbCredentialsError, 36 | validationError, 37 | sessionDeleted 38 | } 39 | 40 | enum ResponseCode { 41 | success, 42 | notFound, 43 | error, 44 | } 45 | 46 | typedef LogoutCallback = void Function(LogoutMethod method); 47 | 48 | class RepoResponse { 49 | RepoResponse({required this.code, this.result}); 50 | 51 | final ResponseCode code; 52 | final T? result; 53 | 54 | @override 55 | bool operator ==(Object other) { 56 | if (identical(other, this)) return true; 57 | return other is RepoResponse && 58 | other.code == this.code && 59 | other.result == this.result; 60 | } 61 | } 62 | 63 | class ReceivedNotification { 64 | ReceivedNotification( 65 | {required this.id, 66 | required this.title, 67 | required this.body, 68 | required this.payload}); 69 | 70 | final int id; 71 | final String title; 72 | final String body; 73 | final String payload; 74 | } 75 | 76 | class Repository { 77 | Repository._internal() { 78 | _database = AppDatabase.instance; 79 | } 80 | 81 | late AppDatabase _database; 82 | 83 | static final Repository instance = Repository._internal(); 84 | final _isLoggedInSubject = BehaviorSubject.seeded(false); 85 | final _lastLogoutMethodSubject = 86 | BehaviorSubject.seeded(LogoutMethod.normal); 87 | 88 | Stream get isLoggedIn => _isLoggedInSubject.stream; 89 | Stream get lastLogoutMethod => _lastLogoutMethodSubject.stream; 90 | 91 | Future login(Environment environment, String username, String password, 92 | Function(LoginResult) callback) async { 93 | try { 94 | var response = await ApiProvider.instance 95 | .login(username, password, onLogout: triggerLogout); 96 | 97 | if (response.statusCode == 200) { 98 | var success = await _database.login(username, password); 99 | if (success) { 100 | callback(LoginResult.authorized); 101 | _isLoggedInSubject.add(true); 102 | } else { 103 | callback(LoginResult.error); 104 | } 105 | } else if (response.statusCode == 401) { 106 | callback(LoginResult.unauthorized); 107 | } else { 108 | callback(LoginResult.error); 109 | } 110 | } catch (e) { 111 | debugPrint(e.toString()); 112 | callback(LoginResult.disconnected); 113 | } 114 | } 115 | 116 | void triggerLogout(LogoutMethod method) { 117 | _isLoggedInSubject.add(false); 118 | _lastLogoutMethodSubject.add(method); 119 | } 120 | 121 | // Call this once all streams / listeners have been cleaned up ( Your homepage ) 122 | Future logout() async { 123 | await _database.logout(); 124 | } 125 | 126 | void dispose() async { 127 | await _isLoggedInSubject.close(); 128 | await _lastLogoutMethodSubject.close(); 129 | } 130 | 131 | ObservableResponse> getBeer( 132 | int limit, int offset, bool isDescending) => 133 | _database.getBeer(limit, offset, isDescending); 134 | } 135 | -------------------------------------------------------------------------------- /example/lib/models/database/beer.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of beer; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializer _$beerSerializer = new _$BeerSerializer(); 10 | 11 | class _$BeerSerializer implements StructuredSerializer { 12 | @override 13 | final Iterable types = const [Beer, _$Beer]; 14 | @override 15 | final String wireName = 'Beer'; 16 | 17 | @override 18 | Iterable serialize(Serializers serializers, Beer object, 19 | {FullType specifiedType = FullType.unspecified}) { 20 | final result = []; 21 | Object? value; 22 | value = object.beerID; 23 | if (value != null) { 24 | result 25 | ..add('beerID') 26 | ..add(serializers.serialize(value, 27 | specifiedType: const FullType(String))); 28 | } 29 | value = object.name; 30 | if (value != null) { 31 | result 32 | ..add('name') 33 | ..add(serializers.serialize(value, 34 | specifiedType: const FullType(String))); 35 | } 36 | return result; 37 | } 38 | 39 | @override 40 | Beer deserialize(Serializers serializers, Iterable serialized, 41 | {FullType specifiedType = FullType.unspecified}) { 42 | final result = new BeerBuilder(); 43 | 44 | final iterator = serialized.iterator; 45 | while (iterator.moveNext()) { 46 | final key = iterator.current as String; 47 | iterator.moveNext(); 48 | final Object? value = iterator.current; 49 | switch (key) { 50 | case 'beerID': 51 | result.beerID = serializers.deserialize(value, 52 | specifiedType: const FullType(String)) as String?; 53 | break; 54 | case 'name': 55 | result.name = serializers.deserialize(value, 56 | specifiedType: const FullType(String)) as String?; 57 | break; 58 | } 59 | } 60 | 61 | return result.build(); 62 | } 63 | } 64 | 65 | class _$Beer extends Beer { 66 | @override 67 | final String? beerID; 68 | @override 69 | final String? name; 70 | 71 | factory _$Beer([void Function(BeerBuilder)? updates]) => 72 | (new BeerBuilder()..update(updates)).build(); 73 | 74 | _$Beer._({this.beerID, this.name}) : super._(); 75 | 76 | @override 77 | Beer rebuild(void Function(BeerBuilder) updates) => 78 | (toBuilder()..update(updates)).build(); 79 | 80 | @override 81 | BeerBuilder toBuilder() => new BeerBuilder()..replace(this); 82 | 83 | @override 84 | bool operator ==(Object other) { 85 | if (identical(other, this)) return true; 86 | return other is Beer && beerID == other.beerID && name == other.name; 87 | } 88 | 89 | @override 90 | int get hashCode { 91 | return $jf($jc($jc(0, beerID.hashCode), name.hashCode)); 92 | } 93 | 94 | @override 95 | String toString() { 96 | return (newBuiltValueToStringHelper('Beer') 97 | ..add('beerID', beerID) 98 | ..add('name', name)) 99 | .toString(); 100 | } 101 | } 102 | 103 | class BeerBuilder implements Builder { 104 | _$Beer? _$v; 105 | 106 | String? _beerID; 107 | String? get beerID => _$this._beerID; 108 | set beerID(String? beerID) => _$this._beerID = beerID; 109 | 110 | String? _name; 111 | String? get name => _$this._name; 112 | set name(String? name) => _$this._name = name; 113 | 114 | BeerBuilder(); 115 | 116 | BeerBuilder get _$this { 117 | final $v = _$v; 118 | if ($v != null) { 119 | _beerID = $v.beerID; 120 | _name = $v.name; 121 | _$v = null; 122 | } 123 | return this; 124 | } 125 | 126 | @override 127 | void replace(Beer other) { 128 | ArgumentError.checkNotNull(other, 'other'); 129 | _$v = other as _$Beer; 130 | } 131 | 132 | @override 133 | void update(void Function(BeerBuilder)? updates) { 134 | if (updates != null) updates(this); 135 | } 136 | 137 | @override 138 | _$Beer build() { 139 | final _$result = _$v ?? new _$Beer._(beerID: beerID, name: name); 140 | replace(_$result); 141 | return _$result; 142 | } 143 | } 144 | 145 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new 146 | -------------------------------------------------------------------------------- /lib/src/fragment.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | /// Fragment provides readonly access to data value. Fragment also provides subscript access by 4 | /// either key or index to the nested values which are wrapped by Fragment objects. 5 | class Fragment { 6 | Fragment._init(dynamic value) { 7 | _value = value; 8 | } 9 | 10 | dynamic _value; 11 | 12 | /// Checks whether the value held by the fragment object exists or is nil value or not. 13 | bool get exists => _value != null; 14 | 15 | /// Gets a property's value as a boolean value. 16 | /// Returns true if the value exists, and is either `true` or a nonzero number. 17 | /// 18 | /// - Returns: The Bool value. 19 | bool? getBoolean() { 20 | if (_value is num) { 21 | return _value != 0; 22 | } 23 | 24 | return _value is bool ? _value : false; 25 | } 26 | 27 | /// Gets a property's value as a double value. 28 | /// Integers will be converted to double. The value `true` is returned as 1.0, `false` as 0.0. 29 | /// Returns 0.0 if the property doesn't exist or does not have a numeric value. 30 | /// 31 | /// - Returns: The Double value. 32 | double? getDouble() { 33 | if (_value is double) { 34 | return _value; 35 | } else if (_value is int) { 36 | return _value.toDouble(); 37 | } else { 38 | return 0.0; 39 | } 40 | } 41 | 42 | /// Gets a property's value as an int value. 43 | /// Floating point values will be rounded. The value `true` is returned as 1, `false` as 0. 44 | /// Returns 0 if the property doesn't exist or does not have a numeric value. 45 | /// 46 | /// - Returns: The Int value. 47 | int? getInt() { 48 | if (_value is double) { 49 | return _value.toInt(); 50 | } else if (_value is int) { 51 | return _value; 52 | } else { 53 | return 0; 54 | } 55 | } 56 | 57 | /// Get a property’s value as a Blob object without the data. 58 | /// Returns nil if the property doesn’t exist, or its value is not a blob. 59 | /// The blob content will not be stored in the blob, it will be fetched when accessed 60 | /// and the content will return null when there is a discrepancy in the digest, 61 | /// this can happen if the file updated since the last time the document was fetched. 62 | /// 63 | /// - Returns: The Blob object or null. 64 | Blob? getBlob() { 65 | var result = getValue(); 66 | if (result is Blob) { 67 | return result; 68 | } else { 69 | return null; 70 | } 71 | } 72 | 73 | /// Gets a property's value as a string. 74 | /// Returns null if the property doesn't exist, or its value is not a string. 75 | /// 76 | /// - Returns: The String object or null. 77 | String? getString() { 78 | return _value is String ? _value : null; 79 | } 80 | 81 | /// Gets a property's value. The value types are Blob, ArrayObject, 82 | /// DictionaryObject, Number, or String based on the underlying data type; or null 83 | /// if the value is null or the property doesn't exist. 84 | /// 85 | /// - Returns: The value or null. 86 | Object? getValue() { 87 | if (_value is Map) { 88 | if (_value['@type'] == 'blob') { 89 | return Blob._fromMap(_value); 90 | } else { 91 | return Map.from(_value); 92 | } 93 | } else if (_value is List) { 94 | return List.from(_value); 95 | } else { 96 | return _value; 97 | } 98 | } 99 | 100 | /// Get a property's value as a List Object, which is a mapping object of an array value. 101 | /// Returns null if the property doesn't exists, or its value is not an array. 102 | /// 103 | /// - Returns: The List Object object or null. 104 | List? getList() { 105 | var result = getValue(); 106 | if (result is List) { 107 | return List.castFrom(result); 108 | } 109 | 110 | return null; 111 | } 112 | 113 | /// Get a property's value as a Map Object, which is a mapping object of 114 | /// a dictionary value. 115 | /// Returns null if the property doesn't exists, or its value is not a dictionary. 116 | /// 117 | /// - Returns: The Map Object object or nil. 118 | Map? getMap() { 119 | var result = getValue(); 120 | if (result is Map) { 121 | return Map.castFrom(result); 122 | } 123 | 124 | return null; 125 | } 126 | 127 | /// Subscript access to a Fragment object by key. 128 | Fragment operator [](dynamic key) { 129 | if (key is int && _value is List) { 130 | if (key < _value.length) { 131 | return Fragment._init(_value[key]); 132 | } 133 | } else if (key is String && _value is Map) { 134 | return Fragment._init(_value[key]); 135 | } 136 | 137 | return Fragment._init(null); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /example/test/api_server_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2020-present the Saltech Systems authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:convert'; 17 | import 'dart:io'; 18 | 19 | import 'package:http/http.dart'; 20 | import 'package:http/src/utils.dart'; 21 | import 'package:pedantic/pedantic.dart'; 22 | import 'package:flutter_test/flutter_test.dart'; 23 | import 'package:matcher/src/type_matcher.dart'; 24 | 25 | export 'http_utils.dart'; 26 | 27 | /// The current server instance. 28 | HttpServer? _server; 29 | 30 | /// The URL for the current server instance. 31 | Uri get serverUrl => Uri.parse('http://localhost:${_server!.port}'); 32 | 33 | /// Starts a new HTTP server. 34 | Future startServer() async { 35 | _server = (await HttpServer.bind('localhost', 0)) 36 | ..listen((request) async { 37 | var path = request.uri.path; 38 | var response = request.response; 39 | 40 | if (path == '/oauth/v2/token') { 41 | response 42 | ..statusCode = 200 43 | ..contentLength = 0; 44 | unawaited(response.close()); 45 | return; 46 | } 47 | 48 | if (path == '/error') { 49 | response 50 | ..statusCode = 400 51 | ..contentLength = 0; 52 | unawaited(response.close()); 53 | return; 54 | } 55 | 56 | if (path == '/loop') { 57 | var n = int.parse(request.uri.query); 58 | response 59 | ..statusCode = 302 60 | ..headers 61 | .set('location', serverUrl.resolve('/loop?${n + 1}').toString()) 62 | ..contentLength = 0; 63 | unawaited(response.close()); 64 | return; 65 | } 66 | 67 | if (path == '/redirect') { 68 | response 69 | ..statusCode = 302 70 | ..headers.set('location', serverUrl.resolve('/').toString()) 71 | ..contentLength = 0; 72 | unawaited(response.close()); 73 | return; 74 | } 75 | 76 | if (path == '/no-content-length') { 77 | response 78 | ..statusCode = 200 79 | ..contentLength = -1 80 | ..write('body'); 81 | unawaited(response.close()); 82 | return; 83 | } 84 | 85 | var requestBodyBytes = await ByteStream(request).toBytes(); 86 | var encodingName = request.uri.queryParameters['response-encoding']; 87 | var outputEncoding = encodingName == null 88 | ? ascii 89 | : requiredEncodingForCharset(encodingName); 90 | 91 | response.headers.contentType = 92 | ContentType('application', 'json', charset: outputEncoding.name); 93 | response.headers.set('single', 'value'); 94 | 95 | dynamic requestBody; 96 | if (requestBodyBytes.isEmpty) { 97 | requestBody = null; 98 | } else if (request.headers.contentType?.charset != null) { 99 | var encoding = 100 | requiredEncodingForCharset(request.headers.contentType!.charset!); 101 | requestBody = encoding.decode(requestBodyBytes); 102 | } else { 103 | requestBody = requestBodyBytes; 104 | } 105 | 106 | var content = { 107 | 'method': request.method, 108 | 'path': request.uri.path, 109 | 'headers': {} 110 | }; 111 | if (requestBody != null) content['body'] = requestBody; 112 | request.headers.forEach((name, values) { 113 | // These headers are automatically generated by dart:io, so we don't 114 | // want to test them here. 115 | if (name == 'cookie' || name == 'host') return; 116 | 117 | content['headers'][name] = values; 118 | }); 119 | 120 | var body = json.encode(content); 121 | response 122 | ..contentLength = body.length 123 | ..write(body); 124 | unawaited(response.close()); 125 | }); 126 | } 127 | 128 | /// Stops the current HTTP server. 129 | void stopServer() { 130 | if (_server != null) { 131 | _server!.close(); 132 | _server = null; 133 | } 134 | } 135 | 136 | /// A matcher for functions that throw HttpException. 137 | Matcher get throwsClientException => throwsA(TypeMatcher()); 138 | 139 | /// A matcher for functions that throw SocketException. 140 | final Matcher throwsSocketException = 141 | throwsA(const TypeMatcher()); 142 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:couchbase_lite/couchbase_lite.dart'; 4 | import 'package:couchbase_lite_example/beer_sample_app.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | void main() => runApp(BeerSampleApp(AppMode.production)); 9 | //void main() => runApp(ExampleApp()); 10 | 11 | class ExampleApp extends StatefulWidget { 12 | @override 13 | _ExampleAppState createState() => _ExampleAppState(); 14 | } 15 | 16 | class _ExampleAppState extends State { 17 | String _displayString = 'Initializing'; 18 | late Database database; 19 | Replicator? replicator; 20 | late ListenerToken _listenerToken; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | initPlatformState(); 26 | } 27 | 28 | Future runExample() async { 29 | // Platform messages may fail, so we use a try/catch PlatformException. 30 | try { 31 | database = await Database.initWithName("gettingStarted"); 32 | } on PlatformException { 33 | return "Error initializing database"; 34 | } 35 | 36 | // Create a new document (i.e. a record) in the database. 37 | MutableDocument? mutableDoc = 38 | MutableDocument().setDouble("version", 2.0).setString("type", "SDK"); 39 | 40 | // Save it to the database. 41 | try { 42 | await database.saveDocument(mutableDoc); 43 | } on PlatformException { 44 | return "Error saving document"; 45 | } 46 | 47 | // Update a document. 48 | mutableDoc = (await database.document(mutableDoc.id!)) 49 | ?.toMutable() 50 | .setString("language", "Dart"); 51 | 52 | if (mutableDoc != null) { 53 | // Save it to the database. 54 | try { 55 | await database.saveDocument(mutableDoc); 56 | 57 | var document = await (database.document(mutableDoc.id!)); 58 | 59 | // Log the document ID (generated by the database) 60 | // and properties 61 | print("Document ID :: ${document!.id}"); 62 | print("Learning ${document.getString("language")}"); 63 | } on PlatformException { 64 | return "Error saving document"; 65 | } 66 | } 67 | 68 | // Create a query to fetch documents of type SDK. 69 | var query = QueryBuilder.select([SelectResult.all()]) 70 | .from("gettingStarted") 71 | .where(Expression.property("type").equalTo(Expression.string("SDK"))); 72 | 73 | // Run the query. 74 | try { 75 | var result = await query.execute(); 76 | print("Number of rows :: ${result.allResults().length}"); 77 | } on PlatformException { 78 | return "Error running the query"; 79 | } 80 | 81 | // Note wss://10.0.2.2:4984/my-database is for the android simulator on your local machine's couchbase database 82 | // Create replicators to push and pull changes to and from the cloud. 83 | ReplicatorConfiguration config = 84 | ReplicatorConfiguration(database, "ws://10.0.2.2:4984/beer-sample"); 85 | config.replicatorType = ReplicatorType.pushAndPull; 86 | config.continuous = true; 87 | 88 | // Add authentication. 89 | config.authenticator = BasicAuthenticator("foo", "bar"); 90 | 91 | // Create replicator (make sure to add an instance or static variable named replicator) 92 | var replicator = Replicator(config); 93 | 94 | // Listen to replicator change events. 95 | _listenerToken = replicator.addChangeListener((ReplicatorChange event) { 96 | if (event.status.error != null) { 97 | print("Error: " + event.status.error!); 98 | } 99 | 100 | print(event.status.activity.toString()); 101 | }); 102 | 103 | // Start replication. 104 | await replicator.start(); 105 | return "Database and Replicator Started"; 106 | } 107 | 108 | // Platform messages are asynchronous, so we initialize in an async method. 109 | Future initPlatformState() async { 110 | var result = await runExample(); 111 | 112 | // If the widget was removed from the tree while the asynchronous platform 113 | // message was in flight, we want to discard the reply rather than calling 114 | // setState to update our non-existent appearance. 115 | if (!mounted) return; 116 | 117 | setState(() { 118 | _displayString = result; 119 | }); 120 | } 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | return MaterialApp( 125 | home: Scaffold( 126 | appBar: AppBar( 127 | title: const Text('Plugin example app'), 128 | ), 129 | body: Center( 130 | child: Text(_displayString), 131 | ), 132 | ), 133 | ); 134 | } 135 | 136 | @override 137 | void dispose() async { 138 | await replicator?.removeChangeListener(_listenerToken); 139 | await replicator?.stop(); 140 | await replicator?.dispose(); 141 | await database.close(); 142 | 143 | super.dispose(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/src/query/query.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class Query { 4 | final queryId = Uuid().v1(); 5 | bool _stored = false; 6 | Map _options = {}; 7 | Parameters get parameters => throw UnimplementedError(); 8 | Map tokens = {}; 9 | 10 | Map get options => Map.from(_options); 11 | 12 | static const JSONMethodCodec _jsonMethod = JSONMethodCodec(); 13 | static const MethodChannel _channel = 14 | MethodChannel('com.saltechsystems.couchbase_lite/json', _jsonMethod); 15 | static const EventChannel _queryEventChannel = EventChannel( 16 | 'com.saltechsystems.couchbase_lite/queryEventChannel', _jsonMethod); 17 | static final Stream _stream = _queryEventChannel.receiveBroadcastStream(); 18 | 19 | /// Executes the query. 20 | /// 21 | /// Returns the ResultSet object representing the query result. 22 | Future execute() async { 23 | _options['queryId'] = queryId; 24 | 25 | if (!_stored && tokens.isNotEmpty) { 26 | _stored = await _channel.invokeMethod('storeQuery', this).then((bool? value) => value ?? false); 27 | } 28 | 29 | try { 30 | final resultSet = await _channel.invokeMethod('executeQuery', this); 31 | 32 | var results = []; 33 | for (dynamic result in resultSet) { 34 | var newResult = Result(); 35 | newResult.setMap(result['map']); 36 | newResult.setList(result['list']); 37 | newResult.setKeys(List.castFrom(result['keys'])); 38 | results.add(newResult); 39 | } 40 | 41 | return ResultSet(results); 42 | } on PlatformException { 43 | // Remove all listeners on error 44 | for (var token in List.from(tokens.keys)) { 45 | await removeChangeListener(token); 46 | } 47 | 48 | rethrow; 49 | } 50 | } 51 | 52 | /// Adds a query change listener and posts changes to [callback]. 53 | /// 54 | /// Returns the listener token object for removing the listener. 55 | Future addChangeListener( 56 | Function(QueryChange) callback) async { 57 | var token = ListenerToken(); 58 | tokens[token] = 59 | _stream.where((data) => data['query'] == queryId).listen((data) { 60 | Map qcJson = data; 61 | final List? resultList = qcJson['results']; 62 | 63 | ResultSet? result; 64 | 65 | if (resultList != null) { 66 | var results = []; 67 | for (dynamic result in resultList) { 68 | var newResult = Result(); 69 | newResult.setMap(result['map']); 70 | newResult.setList(result['list']); 71 | newResult.setKeys(List.castFrom(result['keys'])); 72 | results.add(newResult); 73 | } 74 | result = ResultSet(results); 75 | } 76 | 77 | String? error = qcJson['error']; 78 | 79 | callback(QueryChange(query: this, results: result, error: error)); 80 | }); 81 | 82 | if (tokens[token] == null) { 83 | // Listener didn't subscribe to stream 84 | tokens.remove(token); 85 | return null; 86 | } 87 | 88 | return token; 89 | } 90 | 91 | /// Removes a change listener wih the given listener token. 92 | Future removeChangeListener(ListenerToken? token) async { 93 | final subscription = tokens.remove(token); 94 | 95 | if (subscription != null) { 96 | await subscription.cancel(); 97 | } 98 | 99 | if (_stored && tokens.isEmpty) { 100 | // We had to store this before listening to so if stored on the platform 101 | _stored = !(await _channel.invokeMethod('removeQuery', this).then((bool? value) => value ?? false)); 102 | } 103 | } 104 | 105 | /// Returns a string describing the implementation of the compiled query. 106 | /// 107 | /// This is intended to be read by a developer for purposes of optimizing the query, 108 | /// especially to add database indexes. It’s not machine-readable and its format may change. 109 | /// As currently implemented, the result is two or more lines separated by newline characters: 110 | /// The first line is the SQLite SELECT statement. 111 | /// The subsequent lines are the output of SQLite’s “EXPLAIN QUERY PLAN” command 112 | /// applied to that statement; 113 | /// for help interpreting this, see https://www.sqlite.org/eqp.html . 114 | /// The most important thing to know is that if you see “SCAN TABLE”, 115 | /// it means that SQLite is doing a slow linear scan of the documents instead of using an index. 116 | /// 117 | Future explain() { 118 | //Make sure the queryId is available when the toJson() method is called. 119 | _options['queryId'] = queryId; 120 | return _channel.invokeMethod('explainQuery', this).then((String? value) => value ?? ''); 121 | } 122 | 123 | Map toJson() => options; 124 | } 125 | 126 | class QueryChange { 127 | QueryChange({required this.query, this.results, this.error}); 128 | 129 | final Query query; 130 | final ResultSet? results; 131 | final String? error; 132 | } 133 | -------------------------------------------------------------------------------- /test/document_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:couchbase_lite/couchbase_lite.dart'; 5 | 6 | void main() { 7 | Map? initializer; 8 | late Document document; 9 | late MutableDocument mutableDocument; 10 | setUp(() { 11 | initializer = Map(); 12 | initializer!['string'] = "string"; 13 | initializer!['double'] = 3.14; 14 | initializer!['int'] = 12; 15 | initializer!['map'] = {}; 16 | initializer!['boolInt'] = 0; 17 | initializer!['bool'] = true; 18 | initializer!['list'] = []; 19 | document = MutableDocument(data: initializer, id: "123456789"); 20 | mutableDocument = MutableDocument(); 21 | }); 22 | 23 | test("Document: getting string", () { 24 | expect(document.count(), initializer!.length); 25 | }); 26 | test("Document: getting string", () { 27 | expect(document.getString('string'), "string"); 28 | }); 29 | test("Document: getting double", () { 30 | expect(document.getDouble('double'), 3.14); 31 | expect(document.getInt('double'), 3.14.toInt()); 32 | }); 33 | test("Document: getting int", () { 34 | expect(document.getInt('int'), 12); 35 | }); 36 | test("Document: getting double int", () { 37 | expect(document.getDouble('int'), 12); 38 | }); 39 | test("Document: getting map", () { 40 | expect(document.getMap('map'), {}); 41 | }); 42 | test("Document: getting list", () { 43 | // ignore: deprecated_member_use_from_same_package 44 | expect(document.getArray('list'), []); 45 | expect(document.getList('list'), []); 46 | }); 47 | test("Document: to map", () { 48 | expect(document.toMap(), initializer); 49 | }); 50 | test("Document: getting bool", () { 51 | expect(document.getBoolean("bool"), true); 52 | }); 53 | test("Document: getting bool int", () { 54 | expect(document.getBoolean("boolInt"), false); 55 | }); 56 | test("Document: null list", () { 57 | expect(document.getMap("null"), null); 58 | }); 59 | test("Document: null list", () { 60 | // ignore: deprecated_member_use_from_same_package 61 | expect(document.getArray("null"), null); 62 | expect(document.getList("null"), null); 63 | }); 64 | test("Document: invalid map", () { 65 | expect(document.getMap("boolInt"), null); 66 | }); 67 | test("Document: invalid map", () { 68 | // ignore: deprecated_member_use_from_same_package 69 | expect(document.getArray("boolInt"), null); 70 | }); 71 | test("Document: getting getKeys", () { 72 | expect(document.getKeys(), initializer!.keys); 73 | }); 74 | test("Document: getting id", () { 75 | expect(document.id, "123456789"); 76 | }); 77 | test("mutableDocument: setting string", () { 78 | mutableDocument.setString('string', 'string'); 79 | expect(mutableDocument.getString('string'), "string"); 80 | }); 81 | test("mutableDocument: setting double", () { 82 | mutableDocument.setDouble('double', 3.14); 83 | expect(mutableDocument.getDouble('double'), 3.14); 84 | expect(mutableDocument.getInt('double'), 3.14.toInt()); 85 | }); 86 | test("mutableDocument: setting int", () { 87 | mutableDocument.setInt('int', 12); 88 | expect(mutableDocument.getInt('int'), 12); 89 | }); 90 | test("mutableDocument: setting map", () { 91 | mutableDocument.setMap('map', {"test": true}); 92 | expect(mutableDocument.getMap('map'), {"test": true}); 93 | }); 94 | test("mutableDocument: setting list", () { 95 | // ignore: deprecated_member_use_from_same_package 96 | mutableDocument.setArray('list', []); 97 | // ignore: deprecated_member_use_from_same_package 98 | expect(mutableDocument.getArray('list'), []); 99 | mutableDocument.setList('list', []); 100 | expect(mutableDocument.getList('list'), []); 101 | }); 102 | test("mutableDocument: null map", () { 103 | expect(mutableDocument.getMap("null"), null); 104 | }); 105 | test("mutableDocument: null list", () { 106 | // ignore: deprecated_member_use_from_same_package 107 | expect(mutableDocument.getArray("null"), null); 108 | expect(mutableDocument.getList("null"), null); 109 | }); 110 | test("mutableDocument: invalid list", () { 111 | expect(mutableDocument.getMap("boolInt"), null); 112 | }); 113 | test("mutableDocument: invalid map", () { 114 | expect(mutableDocument.getList("boolInt"), null); 115 | }); 116 | test("mutableDocument: to map", () { 117 | mutableDocument.setBoolean("bool", true); 118 | mutableDocument.setInt('int', 12); 119 | mutableDocument.remove('int'); 120 | expect(mutableDocument.toMap(), {"bool": true}); 121 | }); 122 | test("mutableDocument: getting bool", () { 123 | mutableDocument.setBoolean("bool", true); 124 | expect(document.getBoolean("bool"), true); 125 | }); 126 | test("mutableDocument: setting data / toMutable", () { 127 | var map = mutableDocument.toMap(); 128 | mutableDocument.setData(null); 129 | expect(mutableDocument.toMap(), {}); 130 | expect(mutableDocument.getKeys(), []); 131 | mutableDocument.setData(map); 132 | expect(mutableDocument.toMap(), map); 133 | expect(mutableDocument.toMutable().toMap(), map); 134 | }); 135 | test("Blob", () async { 136 | Blob blob = Blob.data("application/octet-stream", Uint8List(0)); 137 | mutableDocument.setBlob("blob", blob); 138 | expect(await mutableDocument.getBlob("blob")!.content, await blob.content); 139 | }); 140 | test("NullDocument", () { 141 | expect(MutableDocument(id: "test").toMap(), {}); 142 | }); 143 | } 144 | -------------------------------------------------------------------------------- /lib/src/mutable_document.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | class MutableDocument extends Document { 4 | MutableDocument({String? id, Map? data}) 5 | : super._init(data, id); 6 | 7 | MutableDocument._init( 8 | [Map? data, String? id, String? dbname, int? sequence]) 9 | : super._init(data, id, dbname, sequence); 10 | 11 | /// Set a value for the given key. Allowed value types are List, Map, 12 | /// Int, Double, Boolean, and String. 13 | /// The Lists and Maps must contain only the above types. 14 | /// 15 | /// - Parameters: 16 | /// - value: The value. 17 | /// - key: The key. 18 | /// - Returns: The self object. 19 | MutableDocument setValue(String key, Object? value) { 20 | if (value is Fragment) { 21 | value = value.getValue(); 22 | } 23 | 24 | if (value != null) { 25 | super._data[key] = value; 26 | } 27 | 28 | return this; 29 | } 30 | 31 | /// Set a List object for the given key. Allowed value types are List, Map, 32 | /// Int, Double, Boolean, and String. 33 | /// 34 | /// - Parameters: 35 | /// - value: The List object. 36 | /// - key: The key. 37 | MutableDocument setList(String key, List value) => 38 | setValue(key, value); 39 | 40 | /// Set a List object for the given key. Allowed value types are List, Map, 41 | /// Int, Double, Boolean, and String. 42 | /// 43 | /// - Parameters: 44 | /// - value: The List object. 45 | /// - key: The key. 46 | @Deprecated('Use `setList`.') 47 | MutableDocument setArray(String key, List value) => 48 | setList(key, value); 49 | 50 | /// Set a Map Object object for the given key. Allowed value types are List, Map, 51 | /// Int, Double, Boolean, and String. 52 | /// 53 | /// - Parameters: 54 | /// - value: The Map object. 55 | /// - key: The key. 56 | MutableDocument setMap(String key, Map value) => 57 | setValue(key, value); 58 | 59 | /// Set a Blob object for the given key. 60 | /// 61 | /// - Parameters: 62 | /// - value: The Blob object. 63 | /// - key: The key. 64 | MutableDocument setBlob(String key, Blob value) => 65 | setValue(key, value.toMap()); 66 | 67 | /// Set a boolean value for the given key. 68 | /// 69 | /// - Parameters: 70 | /// - value: The boolean value. 71 | /// - key: The key. 72 | MutableDocument setBoolean(String key, bool value) => setValue(key, value); 73 | 74 | /// Set a double value for the given key. 75 | /// 76 | /// - Parameters: 77 | /// - value: The double value. 78 | /// - key: The key. 79 | MutableDocument setDouble(String key, double value) => setValue(key, value); 80 | 81 | /// Set an int value for the given key. 82 | /// 83 | /// - Parameters: 84 | /// - value: The int value. 85 | /// - key: The key. 86 | MutableDocument setInt(String key, int value) => setValue(key, value); 87 | 88 | /// Set a String value for the given key. 89 | /// 90 | /// - Parameters: 91 | /// - value: The String value. 92 | /// - key: The Document object. 93 | MutableDocument setString(String key, String value) => setValue(key, value); 94 | 95 | /// Set a Date value for the given key. 96 | /// 97 | /// - Parameters: 98 | /// - value: The UTC DateTime value. 99 | /// - key: The Document object. 100 | /*MutableDocument _setDate(String key, DateTime value) { 101 | if (!(value?.isUtc ?? true)) { 102 | throw ArgumentError.value(value, 'value', 'Must be in utc time.'); 103 | } 104 | 105 | return setValue(key, value?.toIso8601String()); 106 | }*/ 107 | 108 | /// Removes a given key and its value. 109 | /// 110 | /// - Parameter key: The key. 111 | MutableDocument remove(String key) { 112 | super._data.remove(key); 113 | 114 | return this; 115 | } 116 | 117 | /// Set data for the document. Allowed value types are List, Map, 118 | /// Int, Double, Boolean, and String. 119 | /// The Lists and Maps must contain only the above types. 120 | /// 121 | /// - Parameters: 122 | /// - value: The DateTime value. 123 | /// - key: The Document object. 124 | MutableDocument setData(Map? data) { 125 | super._data = _stringMapFromDynamic(data ?? {}); 126 | 127 | return this; 128 | } 129 | 130 | /// Returns the same MutableDocument object. 131 | /// 132 | /// - Returns: The MutableDocument object. 133 | @override 134 | MutableDocument toMutable() { 135 | return MutableDocument._init(toMap(), id, _dbname, sequence); 136 | } 137 | 138 | /// Get a property's value as a List Object, which is a mapping object of an array value. 139 | /// Returns null if the property doesn't exists, or its value is not an array. 140 | /// 141 | /// - Parameter key: The key. 142 | /// - Returns: The List Object object or null. 143 | @override 144 | List? getList(String key) { 145 | var _result = getValue(key); 146 | if (_result is List) { 147 | return List.castFrom(_result); 148 | } 149 | 150 | return null; 151 | } 152 | 153 | /// Get a property's value as a Map Object, which is a mapping object of 154 | /// a dictionary value. 155 | /// Returns nil if the property doesn't exists, or its value is not a dictionary. 156 | /// 157 | /// - Parameter key: The key. 158 | /// - Returns: The Map Object object or nil. 159 | @override 160 | Map? getMap(String key) { 161 | var _result = getValue(key); 162 | if (_result is Map) { 163 | return Map.castFrom(_result); 164 | } 165 | 166 | return null; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/src/query/expression/expression.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | abstract class Expression { 4 | factory Expression.all() { 5 | return PropertyExpression({'property': null}); 6 | } 7 | 8 | factory Expression.booleanValue(bool value) { 9 | return VariableExpression({'booleanValue': value}); 10 | } 11 | 12 | factory Expression.doubleValue(double value) { 13 | return VariableExpression({'doubleValue': value}); 14 | } 15 | 16 | // TODO: Implement date value in Expression 17 | // static Expression date(DateTime value); 18 | 19 | factory Expression.intValue(int value) { 20 | return VariableExpression({value.bitLength > 32 ? 'longValue' : 'intValue': value}); 21 | } 22 | 23 | factory Expression.value(Object value) { 24 | return VariableExpression({'value': value}); 25 | } 26 | 27 | factory Expression.string(String value) { 28 | return VariableExpression({'string': value}); 29 | } 30 | 31 | factory Expression.property(String value) { 32 | return PropertyExpression({'property': value}); 33 | } 34 | 35 | factory Expression.negated(Expression expression) { 36 | return MetaExpression({'negated': expression._internalExpressionStack}); 37 | } 38 | 39 | factory Expression.not(Expression expression) { 40 | return MetaExpression({'not': expression._internalExpressionStack}); 41 | } 42 | 43 | final List> _internalExpressionStack = []; 44 | 45 | List> get internalExpressionStack => 46 | List.from(_internalExpressionStack); 47 | 48 | Expression add(Expression expression) { 49 | return _addExpression('add', expression); 50 | } 51 | 52 | Expression and(Expression expression) { 53 | return _addExpression('and', expression); 54 | } 55 | 56 | Expression between(Expression expression1, Expression expression2) { 57 | return _addExpression('between', expression1, 58 | secondSelector: 'and', secondExpression: expression2); 59 | } 60 | 61 | Expression divide(Expression expression) { 62 | return _addExpression('divide', expression); 63 | } 64 | 65 | Expression equalTo(Expression expression) { 66 | return _addExpression('equalTo', expression); 67 | } 68 | 69 | Expression greaterThan(Expression expression) { 70 | return _addExpression('greaterThan', expression); 71 | } 72 | 73 | Expression greaterThanOrEqualTo(Expression expression) { 74 | return _addExpression('greaterThanOrEqualTo', expression); 75 | } 76 | 77 | // implement in(Expression... expressions) but lacking variable arguments number feature in Dart 78 | Expression iN(List listExpression) { 79 | return _addList('in', listExpression); 80 | } 81 | 82 | Expression iS(Expression expression) { 83 | return _addExpression('is', expression); 84 | } 85 | 86 | Expression isNot(Expression expression) { 87 | return _addExpression('isNot', expression); 88 | } 89 | 90 | Expression isNullOrMissing() { 91 | var clone = _clone(); 92 | clone._internalExpressionStack.add({'isNullOrMissing': null}); 93 | return clone; 94 | } 95 | 96 | Expression lessThan(Expression expression) { 97 | return _addExpression('lessThan', expression); 98 | } 99 | 100 | Expression lessThanOrEqualTo(Expression expression) { 101 | return _addExpression('lessThanOrEqualTo', expression); 102 | } 103 | 104 | Expression like(Expression expression) { 105 | return _addExpression('like', expression); 106 | } 107 | 108 | Expression modulo(Expression expression) { 109 | return _addExpression('modulo', expression); 110 | } 111 | 112 | Expression multiply(Expression expression) { 113 | return _addExpression('multiply', expression); 114 | } 115 | 116 | Expression notEqualTo(Expression expression) { 117 | return _addExpression('notEqualTo', expression); 118 | } 119 | 120 | Expression notNullOrMissing() { 121 | var clone = _clone(); 122 | clone._internalExpressionStack.add({'notNullOrMissing': null}); 123 | return clone; 124 | } 125 | 126 | Expression or(Expression expression) { 127 | return _addExpression('or', expression); 128 | } 129 | 130 | Expression regex(Expression expression) { 131 | return _addExpression('regex', expression); 132 | } 133 | 134 | Expression subtract(Expression expression) { 135 | return _addExpression('subtract', expression); 136 | } 137 | 138 | Expression from(String alias) { 139 | var fromExpression = _clone(); 140 | fromExpression._internalExpressionStack.add({'from': alias}); 141 | return fromExpression; 142 | } 143 | 144 | Expression _addExpression(String selector, Expression expression, 145 | {String? secondSelector, Expression? secondExpression}) { 146 | var clone = _clone(); 147 | if (secondSelector != null && secondExpression != null) { 148 | clone._internalExpressionStack.add({ 149 | selector: expression.internalExpressionStack, 150 | secondSelector: secondExpression.internalExpressionStack 151 | }); 152 | } else { 153 | clone._internalExpressionStack 154 | .add({selector: expression.internalExpressionStack}); 155 | } 156 | return clone; 157 | } 158 | 159 | Expression _addList(String selector, List listExpression) { 160 | var clone = _clone(); 161 | var json = []; 162 | listExpression.forEach((expression) { 163 | json.add(expression.internalExpressionStack); 164 | }); 165 | clone._internalExpressionStack.add({selector: json}); 166 | return clone; 167 | } 168 | 169 | Expression _clone(); 170 | 171 | List> toJson() => internalExpressionStack; 172 | } 173 | -------------------------------------------------------------------------------- /lib/src/replicator.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | enum ReplicatorActivityLevel { busy, idle, offline, stopped, connecting } 4 | 5 | class Replicator { 6 | Replicator(this.config) { 7 | //this.config._isLocked = true; 8 | _storingReplicator = _jsonChannel.invokeMethod('storeReplicator', this); 9 | } 10 | 11 | static const MethodChannel _methodChannel = 12 | MethodChannel('com.saltechsystems.couchbase_lite/replicator'); 13 | static const JSONMethodCodec _jsonMethod = JSONMethodCodec(); 14 | static const MethodChannel _jsonChannel = 15 | MethodChannel('com.saltechsystems.couchbase_lite/json', _jsonMethod); 16 | static const EventChannel _replicationEventChannel = 17 | EventChannel('com.saltechsystems.couchbase_lite/replicationEventChannel'); 18 | static final Stream _replicationStream = 19 | _replicationEventChannel.receiveBroadcastStream(); 20 | 21 | final replicatorId = Uuid().v1(); 22 | Map tokens = {}; 23 | 24 | final ReplicatorConfiguration config; 25 | Future? _storingReplicator; 26 | 27 | /// Starts the replicator. 28 | /// 29 | /// The replicator runs asynchronously and will report its progress throuh the replicator change notification. 30 | Future start() async { 31 | await _storingReplicator; 32 | 33 | await _methodChannel 34 | .invokeMethod('start', {'replicatorId': replicatorId}); 35 | } 36 | 37 | /// Stops a running replicator. 38 | /// 39 | /// When the replicator actually stops, the replicator will change its status’s activity level to .stopped and the replicator change notification will be notified accordingly. 40 | Future stop() async { 41 | await _storingReplicator; 42 | 43 | await _methodChannel 44 | .invokeMethod('stop', {'replicatorId': replicatorId}); 45 | } 46 | 47 | /// Stops a running replicator. 48 | /// 49 | /// When the replicator actually stops, the replicator will change its status’s activity level to .stopped and the replicator change notification will be notified accordingly. 50 | Future resetCheckpoint() async { 51 | await _storingReplicator; 52 | 53 | await _methodChannel.invokeMethod( 54 | 'resetCheckpoint', {'replicatorId': replicatorId}); 55 | } 56 | 57 | /// Adds a replicator change listener. 58 | /// 59 | /// Returns the listener token object for removing the listener. 60 | ListenerToken addChangeListener(Function(ReplicatorChange) callback) { 61 | var token = ListenerToken(); 62 | tokens[token] = _replicationStream 63 | .where((data) => (data['replicator'] == replicatorId && 64 | data['type'] == 'ReplicatorChange')) 65 | .listen((data) { 66 | var activity = ReplicatorStatus.activityFromString(data['activity']); 67 | String? error; 68 | if (data['error'] is String) { 69 | error = data['error']; 70 | } 71 | 72 | callback( 73 | ReplicatorChange(this, ReplicatorStatus._internal(activity, error))); 74 | }); 75 | return token; 76 | } 77 | 78 | /// Adds a document replicator change listener. 79 | /// 80 | /// Returns the listener token object for removing the listener. 81 | ListenerToken addDocumentReplicationListener( 82 | Function(DocumentReplication) callback) { 83 | var token = ListenerToken(); 84 | tokens[token] = _replicationStream 85 | .where((data) => ((data['replicator'] == replicatorId && 86 | data['type'] == 'DocumentReplication'))) 87 | .listen((data) { 88 | callback(DocumentReplication.fromMap(data)! 89 | .rebuild((b) => b..replicator = this)); 90 | }); 91 | return token; 92 | } 93 | 94 | /// Removes a change listener with the given listener token. 95 | Future removeChangeListener(ListenerToken token) async { 96 | var subscription = tokens.remove(token); 97 | 98 | if (subscription != null) { 99 | await subscription.cancel(); 100 | } 101 | 102 | return token; 103 | } 104 | 105 | /// Removes change listeners and references on the Platform. This should be called when finished with the replicator to prevent memory leaks. 106 | Future dispose() async { 107 | for (var token in List.from(tokens.keys)) { 108 | await removeChangeListener(token); 109 | } 110 | 111 | await _storingReplicator; 112 | 113 | await _methodChannel.invokeMethod( 114 | 'dispose', {'replicatorId': replicatorId}); 115 | } 116 | 117 | Map toJson() { 118 | return {'replicatorId': replicatorId, 'config': config}; 119 | } 120 | } 121 | 122 | class ReplicatorStatus { 123 | ReplicatorStatus._internal(this.activity, this.error); 124 | 125 | final ReplicatorActivityLevel? activity; 126 | final String? error; 127 | 128 | static ReplicatorActivityLevel? activityFromString(String? _status) { 129 | switch (_status) { 130 | case 'BUSY': 131 | return ReplicatorActivityLevel.busy; 132 | break; 133 | case 'IDLE': 134 | return ReplicatorActivityLevel.idle; 135 | break; 136 | case 'OFFLINE': 137 | return ReplicatorActivityLevel.offline; 138 | break; 139 | case 'STOPPED': 140 | return ReplicatorActivityLevel.stopped; 141 | break; 142 | case 'CONNECTING': 143 | return ReplicatorActivityLevel.connecting; 144 | break; 145 | } 146 | 147 | return null; 148 | } 149 | } 150 | 151 | class ReplicatorChange { 152 | ReplicatorChange(this.replicator, this.status); 153 | 154 | final Replicator replicator; 155 | final ReplicatorStatus status; 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/document.dart: -------------------------------------------------------------------------------- 1 | part of couchbase_lite; 2 | 3 | /// Couchbase Lite document. The Document is immutable. 4 | class Document { 5 | Document._init( 6 | [Map? data, this._id, this._dbname, this._sequence]) { 7 | _data = _stringMapFromDynamic(data ?? {}); 8 | } 9 | 10 | late Map _data; 11 | String? _dbname; 12 | String? _id; 13 | int? _sequence; 14 | 15 | String? get id => _id; 16 | int? get sequence => _sequence; 17 | 18 | Map _stringMapFromDynamic(Map _map) { 19 | return Map.castFrom(_map); 20 | } 21 | 22 | /// Tests whether a property exists or not. 23 | /// This can be less expensive than value(forKey:), because it does not have to allocate an 24 | /// object for the property value. 25 | /// 26 | /// - Parameter key: The key. 27 | /// - Returns: True of the property exists, otherwise false. 28 | bool contains(String key) { 29 | return _data.containsKey(key); 30 | } 31 | 32 | /// The number of properties in the document. 33 | int count() { 34 | return _data.length; 35 | } 36 | 37 | /// Gets a property's value as a boolean value. 38 | /// Returns true if the value exists, and is either `true` or a nonzero number. 39 | /// 40 | /// - Parameter key: The key. 41 | /// - Returns: The Bool value. 42 | bool? getBoolean(String key) => this[key].getBoolean(); 43 | 44 | /// Gets a property's value as a double value. 45 | /// Integers will be converted to double. The value `true` is returned as 1.0, `false` as 0.0. 46 | /// Returns 0.0 if the property doesn't exist or does not have a numeric value. 47 | /// 48 | /// - Parameter key: The key. 49 | /// - Returns: The Double value. 50 | double? getDouble(String key) => this[key].getDouble(); 51 | 52 | /// Gets a property's value as an int value. 53 | /// Floating point values will be rounded. The value `true` is returned as 1, `false` as 0. 54 | /// Returns 0 if the property doesn't exist or does not have a numeric value. 55 | /// 56 | /// - Parameter key: The key. 57 | /// - Returns: The Int value. 58 | int? getInt(String key) => this[key].getInt(); 59 | 60 | /// Get a property’s value as a Blob object without the data. 61 | /// Returns nil if the property doesn’t exist, or its value is not a blob. 62 | /// The blob content will not be stored in the blob, it will be fetched when accessed 63 | /// and the content will return null when there is a discrepancy in the digest, 64 | /// this can happen if the file updated since the last time the document was fetched. 65 | /// 66 | /// - Parameter key: The key. 67 | /// - Returns: The Blob object or null. 68 | Blob? getBlob(String key) => this[key].getBlob(); 69 | 70 | /// An array containing all keys, or an empty array if the document has no properties. 71 | List getKeys() { 72 | return _data.keys.toList() as List; 73 | } 74 | 75 | /// Gets a property's value as a string. 76 | /// Returns null if the property doesn't exist, or its value is not a string. 77 | /// 78 | /// - Parameter key: The key. 79 | /// - Returns: The String object or null. 80 | String? getString(String key) => this[key].getString(); 81 | 82 | /// Gets a property's value as a date. 83 | /// Returns null if the property doesn't exist, or its value is not a date. 84 | /// 85 | /// - Parameter key: The key. 86 | /// - Returns: The DateTime object in UTC or null. 87 | /*DateTime _getDate(String key, DateTime value) { 88 | Object _result = getValue(key); 89 | return _result is String ? DateTime.parse(_result).toUtc() : null; 90 | }*/ 91 | 92 | /// Gets a property's value. The value types are Blob, ArrayObject, 93 | /// DictionaryObject, Number, or String based on the underlying data type; or null 94 | /// if the value is null or the property doesn't exist. 95 | /// 96 | /// - Parameter key: The key. 97 | /// - Returns: The value or null. 98 | Object? getValue(String key) => this[key].getValue(); 99 | 100 | /// Get a property's value as a List Object, which is a mapping object of an array value. 101 | /// Returns null if the property doesn't exists, or its value is not an array. 102 | /// 103 | /// - Parameter key: The key. 104 | /// - Returns: The List Object object or null. 105 | List? getList(String key) => this[key].getList(); 106 | 107 | /// Get a property's value as a List Object, which is a mapping object of an array value. 108 | /// Returns null if the property doesn't exists, or its value is not an array. 109 | /// 110 | /// - Parameter key: The key. 111 | /// - Returns: The List Object object or null. 112 | @Deprecated('Use `getList`.') 113 | List? getArray(String key) => getList(key); 114 | 115 | /// Get a property's value as a Map Object, which is a mapping object of 116 | /// a dictionary value. 117 | /// Returns null if the property doesn't exists, or its value is not a dictionary. 118 | /// 119 | /// - Parameter key: The key. 120 | /// - Returns: The Map Object object or nil. 121 | Map? getMap(String key) => this[key].getMap(); 122 | 123 | /// Gets content of the current object as a Dictionary. 124 | /// 125 | /// - Returns: The Dictionary representing the content of the current object. 126 | Map toMap() { 127 | return Map.from(_data); 128 | } 129 | 130 | /// Returns a mutable copy of the document. 131 | /// 132 | /// - Returns: The MutableDocument object. 133 | MutableDocument toMutable() { 134 | return MutableDocument._init(_data, id, _dbname, sequence); 135 | } 136 | 137 | /// Subscript access to a Fragment object by key. 138 | Fragment operator [](String key) => Fragment._init(_data[key]); 139 | } 140 | --------------------------------------------------------------------------------