From 54a38af4bc558ef059f802c911ea3bf6509aac3a Mon Sep 17 00:00:00 2001 From: Daeyeon Jeong Date: Fri, 21 Jul 2023 10:12:03 +0900 Subject: [PATCH 1/5] feat(database): add initial firebase database Signed-off-by: Daeyeon Jeong --- .github/recipe.yaml | 2 +- packages/firebase_database/.clang-format | 3 + packages/firebase_database/.gitignore | 30 + packages/firebase_database/CHANGELOG.md | 3 + packages/firebase_database/LICENSE | 45 ++ packages/firebase_database/README.md | 29 + .../firebase_database/analysis_options.yaml | 7 + packages/firebase_database/example/.gitignore | 38 + packages/firebase_database/example/README.md | 4 + .../example/analysis_options.yaml | 5 + .../firebase_database/data_snapshot_e2e.dart | 154 ++++ .../firebase_database/database_e2e.dart | 93 +++ .../database_reference_e2e.dart | 187 +++++ .../firebase_database/extra_e2e.dart | 40 ++ .../firebase_database_configuration_e2e.dart | 41 ++ .../firebase_database_e2e_test.dart | 56 ++ .../firebase_database/on_disconnect_e2e.dart | 79 ++ .../firebase_database/query_e2e.dart | 508 +++++++++++++ .../example/lib/firebase_options.dart | 46 ++ .../firebase_database/example/lib/main.dart | 140 ++++ .../firebase_database/example/pubspec.yaml | 29 + .../example/test_driver/integration_test.dart | 7 + .../example/tizen/.gitignore | 5 + .../firebase_database/example/tizen/App.cs | 20 + .../example/tizen/Runner.csproj | 19 + .../example/tizen/shared/res/ic_launcher.png | Bin 0 -> 1443 bytes .../example/tizen/tizen-manifest.xml | 16 + packages/firebase_database/pubspec.yaml | 33 + packages/firebase_database/tizen/.gitignore | 5 + .../firebase_database/tizen/build_def.prop | 2 + .../firebase_database/tizen/dep/conversion.cc | 122 ++++ .../tizen/dep/include/common/conversion.h | 29 + .../tizen/dep/include/common/logger.h | 168 +++++ .../tizen/dep/include/common/to_string.h | 71 ++ .../tizen/dep/include/common/trace.h | 102 +++ .../tizen/dep/include/common/utils.h | 72 ++ .../firebase_database/tizen/dep/logger.cc | 203 ++++++ .../firebase_database/tizen/dep/to_string.cc | 159 ++++ packages/firebase_database/tizen/dep/trace.cc | 120 ++++ packages/firebase_database/tizen/dep/utils.cc | 29 + .../inc/firebase_database_tizen_plugin.h | 23 + .../firebase_database/tizen/project_def.prop | 30 + .../firebase_database/tizen/src/constants.h | 63 ++ .../src/firebase_database_tizen_plugin.cc | 677 ++++++++++++++++++ .../tizen/src/firebase_database_utils.cc | 247 +++++++ .../tizen/src/firebase_database_utils.h | 38 + packages/firebase_database/tizen/tar_url.sh | 39 + 47 files changed, 3837 insertions(+), 1 deletion(-) create mode 100644 packages/firebase_database/.clang-format create mode 100644 packages/firebase_database/.gitignore create mode 100644 packages/firebase_database/CHANGELOG.md create mode 100644 packages/firebase_database/LICENSE create mode 100644 packages/firebase_database/README.md create mode 100644 packages/firebase_database/analysis_options.yaml create mode 100644 packages/firebase_database/example/.gitignore create mode 100644 packages/firebase_database/example/README.md create mode 100644 packages/firebase_database/example/analysis_options.yaml create mode 100644 packages/firebase_database/example/integration_test/firebase_database/data_snapshot_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/database_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/database_reference_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/extra_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/firebase_database_configuration_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/firebase_database_e2e_test.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/on_disconnect_e2e.dart create mode 100644 packages/firebase_database/example/integration_test/firebase_database/query_e2e.dart create mode 100644 packages/firebase_database/example/lib/firebase_options.dart create mode 100644 packages/firebase_database/example/lib/main.dart create mode 100644 packages/firebase_database/example/pubspec.yaml create mode 100644 packages/firebase_database/example/test_driver/integration_test.dart create mode 100644 packages/firebase_database/example/tizen/.gitignore create mode 100644 packages/firebase_database/example/tizen/App.cs create mode 100644 packages/firebase_database/example/tizen/Runner.csproj create mode 100644 packages/firebase_database/example/tizen/shared/res/ic_launcher.png create mode 100644 packages/firebase_database/example/tizen/tizen-manifest.xml create mode 100644 packages/firebase_database/pubspec.yaml create mode 100644 packages/firebase_database/tizen/.gitignore create mode 100644 packages/firebase_database/tizen/build_def.prop create mode 100644 packages/firebase_database/tizen/dep/conversion.cc create mode 100644 packages/firebase_database/tizen/dep/include/common/conversion.h create mode 100644 packages/firebase_database/tizen/dep/include/common/logger.h create mode 100644 packages/firebase_database/tizen/dep/include/common/to_string.h create mode 100644 packages/firebase_database/tizen/dep/include/common/trace.h create mode 100644 packages/firebase_database/tizen/dep/include/common/utils.h create mode 100644 packages/firebase_database/tizen/dep/logger.cc create mode 100644 packages/firebase_database/tizen/dep/to_string.cc create mode 100644 packages/firebase_database/tizen/dep/trace.cc create mode 100644 packages/firebase_database/tizen/dep/utils.cc create mode 100644 packages/firebase_database/tizen/inc/firebase_database_tizen_plugin.h create mode 100644 packages/firebase_database/tizen/project_def.prop create mode 100644 packages/firebase_database/tizen/src/constants.h create mode 100644 packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc create mode 100644 packages/firebase_database/tizen/src/firebase_database_utils.cc create mode 100644 packages/firebase_database/tizen/src/firebase_database_utils.h create mode 100755 packages/firebase_database/tizen/tar_url.sh diff --git a/.github/recipe.yaml b/.github/recipe.yaml index e173687..3e63859 100644 --- a/.github/recipe.yaml +++ b/.github/recipe.yaml @@ -1,5 +1,5 @@ plugins: firebase_core: ["tv-7.0"] - firebase_database: [] + firebase_database: ["tv-7.0"] firebase_storage: [] cloud_functions: [] diff --git a/packages/firebase_database/.clang-format b/packages/firebase_database/.clang-format new file mode 100644 index 0000000..5ab0945 --- /dev/null +++ b/packages/firebase_database/.clang-format @@ -0,0 +1,3 @@ +--- +BasedOnStyle: Google +--- diff --git a/packages/firebase_database/.gitignore b/packages/firebase_database/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/packages/firebase_database/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/firebase_database/CHANGELOG.md b/packages/firebase_database/CHANGELOG.md new file mode 100644 index 0000000..6073234 --- /dev/null +++ b/packages/firebase_database/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial release. diff --git a/packages/firebase_database/LICENSE b/packages/firebase_database/LICENSE new file mode 100644 index 0000000..b507088 --- /dev/null +++ b/packages/firebase_database/LICENSE @@ -0,0 +1,45 @@ +``` +Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +``` +Copyright 2017 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` diff --git a/packages/firebase_database/README.md b/packages/firebase_database/README.md new file mode 100644 index 0000000..18f0f0c --- /dev/null +++ b/packages/firebase_database/README.md @@ -0,0 +1,29 @@ +# firebase_database_tizen + +The [Firebase Database for Flutter](https://pub.dev/packages/firebase_database) implementation for Tizen. + +It offers experimental features for using Firebase on Flutter for Tizen. It works by wrapping cross-compiled libraries that are based on the [Firebase C++ SDK](https://github.com/firebase/firebase-cpp-sdk) for Linux. + +# Usage + +To use this package, you need to include `firebase_database_tizen` as a dependency alongside `firebase_database` and `firebase_database_platform_interface` in your `pubspec.yaml`. Please note that `firebase_database_tizen` implementation is not officially endorsed for `firebase_database`. + +```yaml +dependencies: + firebase_database: 10.0.9 + firebase_database_tizen: ^0.1.0 +``` + +Then you can import `firebase_database` in your Dart code: + +```dart +import 'package:firebase_database/firebase_database.dart'; +``` + +# Limitations + +The following features are currently unavailable as they're not supported by the version of Firebase C++ SDK for Linux that this plugin is currently based on. + +- Using Firebase Local Emulator Suite. +- Using `FirebaseDatabase#setPersistenceCacheSizeBytes()` for the on-disk data. +- Using `Query#startAfter()` or `Query#endBefore()` method to add a cursor to a query. diff --git a/packages/firebase_database/analysis_options.yaml b/packages/firebase_database/analysis_options.yaml new file mode 100644 index 0000000..a854094 --- /dev/null +++ b/packages/firebase_database/analysis_options.yaml @@ -0,0 +1,7 @@ +include: ../../analysis_options.yaml + +linter: + rules: + public_member_api_docs: false + depend_on_referenced_packages: false + library_private_types_in_public_api: false diff --git a/packages/firebase_database/example/.gitignore b/packages/firebase_database/example/.gitignore new file mode 100644 index 0000000..f0f3dcb --- /dev/null +++ b/packages/firebase_database/example/.gitignore @@ -0,0 +1,38 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VS Code related +.vscode/ + +# Flutter/Dart/Pub related +/pubspec.lock +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/firebase_database/example/README.md b/packages/firebase_database/example/README.md new file mode 100644 index 0000000..63a83f0 --- /dev/null +++ b/packages/firebase_database/example/README.md @@ -0,0 +1,4 @@ +# Example + +A test app for firebase_database_tizen plugin (e2e). + diff --git a/packages/firebase_database/example/analysis_options.yaml b/packages/firebase_database/example/analysis_options.yaml new file mode 100644 index 0000000..a1fbb31 --- /dev/null +++ b/packages/firebase_database/example/analysis_options.yaml @@ -0,0 +1,5 @@ +# Copyright 2021 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# in the LICENSE file. + +include: ../analysis_options.yaml diff --git a/packages/firebase_database/example/integration_test/firebase_database/data_snapshot_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/data_snapshot_e2e.dart new file mode 100644 index 0000000..2a5259e --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/data_snapshot_e2e.dart @@ -0,0 +1,154 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void setupDataSnapshotTests() { + group('DataSnapshot', () { + late DatabaseReference ref; + + setUp(() async { + ref = FirebaseDatabase.instance.ref('tests'); + + // Wipe the database before each test + await ref.remove(); + }); + + test('it returns the correct key', () async { + final s = await ref.get(); + expect(s.key, 'tests'); + }); + + test('it returns a string value', () async { + await ref.set('foo'); + final s = await ref.get(); + expect(s.value, 'foo'); + }); + + test('it returns a number value', () async { + await ref.set(123); + final s = await ref.get(); + expect(s.value, 123); + }); + + test('it returns a bool value', () async { + await ref.set(false); + final s = await ref.get(); + expect(s.value, false); + }); + + test('it returns a null value', () async { + await ref.set(null); + final s = await ref.get(); + expect(s.value, isNull); + }); + + test('it returns a List value', () async { + final data = [ + 'a', + 2, + true, + ['foo'], + { + 0: 'hello', + 1: 'foo', + } + ]; + await ref.set(data); + final s = await ref.get(); + expect( + s.value, + equals([ + 'a', + 2, + true, + ['foo'], + ['hello', 'foo'] + ]), + ); + }); + + test('it returns a Map value', () async { + final data = {'foo': 'bar'}; + await ref.set(data); + final s = await ref.get(); + expect(s.value, equals(data)); + }); + + test('non-string Map keys are converted to strings', () async { + final data = {1: 'foo', 2: 'bar', 'foo': 'bar'}; + await ref.set(data); + final s = await ref.get(); + expect(s.value, equals({'1': 'foo', '2': 'bar', 'foo': 'bar'})); + }); + + test('setWithPriority returns the correct priority', () async { + await ref.setWithPriority('foo', 1); + final s = await ref.get(); + expect(s.priority, 1); + }); + + test('setPriority returns the correct priority', () async { + await ref.set('foo'); + await ref.setPriority(2); + final s = await ref.get(); + expect(s.priority, 2); + }); + + test('exists returns true', () async { + await ref.set('foo'); + final s = await ref.get(); + expect(s.exists, isTrue); + }); + + test('exists returns false', () async { + final s = await ref.get(); + expect(s.exists, isFalse); + }); + + test('hasChild returns false', () async { + final s = await ref.get(); + expect(s.hasChild('bar'), isFalse); + }); + + test('hasChild returns true', () async { + await ref.set({ + 'foo': {'bar': 'baz'} + }); + final s = await ref.get(); + expect(s.hasChild('bar'), isFalse); + }); + + test('child returns the correct snapshot for lists', () async { + await ref.set([0, 1]); + final s = await ref.get(); + expect(s.child('1'), isA()); + expect(s.child('1').value, 1); + }); + + test('child returns the correct snapshot', () async { + await ref.set({ + 'foo': {'bar': 'baz'} + }); + final s = await ref.get(); + expect(s.child('foo/bar'), isA()); + expect(s.child('foo/bar').value, 'baz'); + }); + + test('children returns the children in order', () async { + await ref.set({ + 'a': 3, + 'b': 2, + 'c': 1, + }); + final s = await ref.orderByValue().get(); + + List children = s.children.toList(); + expect(children[0].value, 1); + expect(children[1].value, 2); + expect(children[2].value, 3); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/database_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/database_e2e.dart new file mode 100644 index 0000000..bf79f23 --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/database_e2e.dart @@ -0,0 +1,93 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +import 'firebase_database_e2e_test.dart'; +import '../../lib/firebase_options.dart'; + +void setupDatabaseTests() { + group('FirebaseDatabase.ref()', () { + setUpAll(() async { + await database.ref('tests/flutterfire').set(0); + }); + + test('returns a correct reference', () async { + final ref = database.ref('tests/flutterfire'); + expect(ref.key, 'flutterfire'); + expect(ref.parent, isNotNull); + expect(ref.parent!.key, 'tests'); + expect(ref.parent!.parent, isNotNull); + expect(ref.parent!.parent?.key, isNull); + + final snapshot = await ref.get(); + expect(snapshot.key, 'flutterfire'); + expect(snapshot.value, 0); + }); + + test( + 'root reference path returns as "/"', + () async { + final rootRef = database.ref(); + expect(rootRef.path, '/'); + expect(rootRef.key, isNull); + expect(rootRef.parent, isNull); + }, + ); + + test( + 'returns a reference to the root of the database if no path specified', + () async { + final rootRef = database.ref(); + expect(rootRef.key, isNull); + expect(rootRef.parent, isNull); + + final childRef = rootRef.child('tests/flutterfire'); + final snapshot = await childRef.get(); + expect(snapshot.key, 'flutterfire'); + expect(snapshot.value, 0); + }, + ); + }); + + group('FirebaseDatabase.refFromURL()', () { + final String databaseURL = + DefaultFirebaseOptions.currentPlatform.databaseURL ?? ''; + + test('correctly returns a ref for database root', () async { + final ref = database.refFromURL(databaseURL); + expect(ref.key, isNull); + + final refWithTrailingSlash = database.refFromURL( + '${databaseURL}/', + ); + expect(refWithTrailingSlash.key, isNull); + }); + + test('correctly returns a ref for any database path', () async { + final ref = database.refFromURL( + '${databaseURL}/foo', + ); + expect(ref.key, 'foo'); + + final refWithNestedPath = database.refFromURL( + '${databaseURL}/foo/bar', + ); + expect(refWithNestedPath.parent?.key, 'foo'); + expect(refWithNestedPath.key, 'bar'); + }); + + test('throws [ArgumentError] if not a valid https:// url', () async { + expect(() => database.refFromURL('foo'), throwsArgumentError); + }); + + test('throws [ArgumentError] if database url does not match instance url', + () async { + expect( + () => database.refFromURL('https://some-other-database.firebaseio.com'), + throwsArgumentError, + ); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/database_reference_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/database_reference_e2e.dart new file mode 100644 index 0000000..33c5fbb --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/database_reference_e2e.dart @@ -0,0 +1,187 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:math'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'firebase_database_e2e_test.dart'; + +void setupDatabaseReferenceTests() { + group('DatabaseReference', () { + late DatabaseReference ref; + + setUp(() async { + ref = database.ref('tests'); + await ref.remove(); + }); + + group('set()', () { + test('sets value', () async { + final v = Random.secure().nextInt(1024); + await ref.set(v); + final actual = await ref.get(); + expect(actual.value, v); + }); + + test( + 'throws "permission-denied" on a ref with no read permission', + () async { + await expectLater( + database.ref('denied_read').get(), + throwsA( + isA() + .having( + (error) => error.code, + 'code', + 'permission-denied', + ) + .having( + (error) => error.message, + 'message', + predicate( + (String message) => + message.contains("doesn't have permission"), + ), + ), + ), + ); + }, + skip: true, // TODO Fails on CI even though works locally + ); + + test('removes a value if set to null', () async { + final v = Random.secure().nextInt(1024); + await ref.set(v); + final before = await ref.get(); + expect(before.value, v); + + await ref.set(null); + final after = await ref.get(); + expect(after.value, isNull); + expect(after.exists, isFalse); + }); + }); + + group('setPriority()', () { + test('sets a priority', () async { + await ref.set('foo'); + await ref.setPriority(2); + final snapshot = await ref.get(); + expect(snapshot.priority, 2); + }); + }); + + group('setWithPriority()', () { + test('sets a non-null value with a non-null priority', () async { + await Future.wait([ + ref.child('first').setWithPriority(1, 10), + ref.child('second').setWithPriority(2, 1), + ref.child('third').setWithPriority(3, 5), + ]); + + final snapshot = await ref.orderByPriority().get(); + final keys = snapshot.children.map((child) => child.key).toList(); + expect(keys, ['second', 'third', 'first']); + }); + }); + + group('update()', () { + test('updates value at given location', () async { + await ref.set({'foo': 'bar'}); + final newValue = Random.secure().nextInt(255) + 1; + await ref.update({'bar': newValue}); + final actual = await ref.get(); + + expect(actual.value, { + 'foo': 'bar', + 'bar': newValue, + }); + }); + }); + + group('runTransaction()', () { + setUp(() async { + await ref.set(0); + }); + + test('aborts a transaction', () async { + await ref.set(5); + final snapshot = await ref.get(); + expect(snapshot.value, 5); + + final result = await ref.runTransaction((value) { + // NOTE(daeyeon): When testing this in firebase (not emulator), the + // value firstly come is `null`, even though we actually set it to 5 + // just above. This is actually expected behavior according to the + // documentation(https://firebase.google.com/docs/database/admin/save-data#transaction-function-is-called-multiple-times). + // This transaction handler can be called multiple times and must be + // able to handle 'null' data. Even if there is existing data in your + // database it may not be locally cached when the transaction function + // is run. + // + // If the value is `null`, the handler returns transaction.success + // with 1 as a next value, and the internal `onCompleteCallback` will + // exit with no error. That means that `committed` in the result will + // be true rather false this TC expected. This TC maybe pass only when + // testing with the emulator that doesn't seem to work with C++ SDK. + // Thus, we skip this test for now until we can verify this TC. + final nextValue = (value as int? ?? 0) + 1; + if (nextValue > 5) { + return Transaction.abort(); + } + return Transaction.success(nextValue); + }); + + expect(result.committed, false); + expect(result.snapshot.value, 5); + }, skip: 'TODO: Review TC for defects.'); + + test('executes transaction', () async { + final snapshot = await ref.get(); + final value = (snapshot.value ?? 0) as int; + final result = await ref.runTransaction((value) { + return Transaction.success((value as int? ?? 0) + 1); + }); + + expect(result.committed, true); + expect((result.snapshot.value ?? 0) as int > value, true); + expect(result.snapshot.key, ref.key); + }); + + test('get primitive list values', () async { + List data = ['first', 'second']; + final FirebaseDatabase database = FirebaseDatabase.instance; + final DatabaseReference ref = database.ref('tests/list-values'); + + await ref.set({'list': data}); + + final transactionResult = await ref.runTransaction((mutableData) { + return Transaction.success(mutableData); + }); + + var value = transactionResult.snapshot.value as dynamic; + expect(value, isNotNull); + expect(value['list'], data); + }); + + test('Server.increment', () async { + final FirebaseDatabase database = FirebaseDatabase.instance; + final DatabaseReference ref = database.ref('tests/server-increment'); + await ref.set(ServerValue.increment(1.5)); + + final snap = await ref.get(); + var value = snap.value; + expect(value, 1.5); + + await ref.set(ServerValue.increment(1)); + final snap2 = await ref.get(); + var value2 = snap2.value; + expect(value2, 2.5); + }); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/extra_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/extra_e2e.dart new file mode 100644 index 0000000..4ca2e58 --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/extra_e2e.dart @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void setupExtraTests() { + group('Database Extra', () { + test('PurgeOutstandingWrites()', () async { + FirebaseDatabase.instance.purgeOutstandingWrites(); + }); + }); + + group('Query Extra', () { + late DatabaseReference ref; + + setUp(() async { + ref = FirebaseDatabase.instance.ref('tests'); + + // Wipe the database before each test + await ref.remove(); + }); + + test('keepSynced()', () async { + ref.keepSynced(true); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/firebase_database_configuration_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/firebase_database_configuration_e2e.dart new file mode 100644 index 0000000..7cb4b66 --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/firebase_database_configuration_e2e.dart @@ -0,0 +1,41 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'firebase_database_e2e_test.dart'; + +const MAX_CACHE_SIZE = 100 * 1024 * 1024; +const MIN_CACHE_SIZE = 1042 * 1024; + +void setupConfigurationTests() { + group('FirebaseDatabase configuration', () { + test( + 'setPersistenceCacheSizeBytes Integer', + () { + database.setPersistenceCacheSizeBytes(MIN_CACHE_SIZE); + }, + // Skipped because it is not supported on web + skip: kIsWeb, + ); + + test( + 'setPersistenceCacheSizeBytes Long', + () { + database.setPersistenceCacheSizeBytes(MAX_CACHE_SIZE); + }, + // Skipped because it is not supported on web + skip: kIsWeb, + ); + + test('setLoggingEnabled to true', () { + database.setLoggingEnabled(true); + }); + + test('setLoggingEnabled to false', () { + database.setLoggingEnabled(false); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/firebase_database_e2e_test.dart b/packages/firebase_database/example/integration_test/firebase_database/firebase_database_e2e_test.dart new file mode 100644 index 0000000..f9e15b0 --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/firebase_database_e2e_test.dart @@ -0,0 +1,56 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:tests/firebase_options.dart'; + +import 'data_snapshot_e2e.dart'; +import 'database_e2e.dart'; +import 'database_reference_e2e.dart'; +import 'firebase_database_configuration_e2e.dart'; +import 'query_e2e.dart'; +import 'extra_e2e.dart'; +import 'on_disconnect_e2e.dart'; + +late FirebaseDatabase database; + +// The port we've set the Firebase Database emulator to run on via the +// `firebase.json` configuration file. +const emulatorPort = 9000; + +// Android device emulators consider localhost of the host machine as 10.0.2.2 +// so let's use that if running on Android. +final emulatorHost = + (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) + ? '10.0.2.2' + : 'localhost'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('firebase_database', () { + setUpAll(() async { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + database = FirebaseDatabase.instance; + database.useDatabaseEmulator(emulatorHost, emulatorPort); + await database.goOnline(); + }); + + setupConfigurationTests(); + setupDatabaseTests(); + setupDatabaseReferenceTests(); + setupQueryTests(); + setupDataSnapshotTests(); + setupExtraTests(); + setupOnDisconnectTests(); + // TODO(ehesp): Fix broken tests + // runOnDisconnectTests(); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/on_disconnect_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/on_disconnect_e2e.dart new file mode 100644 index 0000000..33a2b10 --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/on_disconnect_e2e.dart @@ -0,0 +1,79 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void setupOnDisconnectTests() { + group('OnDisconnect', () { + late FirebaseDatabase database; + late DatabaseReference ref; + + setUp(() async { + database = FirebaseDatabase.instance; + ref = database.ref('tests'); + + // Wipe the database before each test + await ref.remove(); + }); + + Future toggleState() async { + await database.goOffline(); + await database.goOnline(); + } + + tearDown(() async { + await FirebaseDatabase.instance.goOnline(); + }); + + test('sets a value on disconnect', () async { + await ref.onDisconnect().set('foo'); + await toggleState(); + var snapshot = await ref.get(); + expect(snapshot.value, 'foo'); + }); + + test('sets a value with priority on disconnect', () async { + await ref.onDisconnect().setWithPriority('foo', 3); + await toggleState(); + var snapshot = await ref.get(); + expect(snapshot.value, 'foo'); + expect(snapshot.priority, 3); + }); + + test('removes a node on disconnect', () async { + await ref.set('foo'); + await ref.onDisconnect().remove(); + await toggleState(); + var snapshot = await ref.get(); + expect(snapshot.exists, isFalse); + }); + + test('updates a node on disconnect', () async { + await ref.set({'foo': 'bar'}); + await ref.onDisconnect().update({'bar': 'baz'}); + await toggleState(); + var snapshot = await ref.get(); + expect( + snapshot.value, + equals({ + 'foo': 'bar', + 'bar': 'baz', + }), + ); + }); + + test('cancels disconnect operations', () async { + await ref.set('foo'); + await ref.onDisconnect().remove(); + await ref.onDisconnect().cancel(); + await toggleState(); + var snapshot = await ref.get(); + expect( + snapshot.value, + 'foo', + ); + }); + }); +} diff --git a/packages/firebase_database/example/integration_test/firebase_database/query_e2e.dart b/packages/firebase_database/example/integration_test/firebase_database/query_e2e.dart new file mode 100644 index 0000000..572459d --- /dev/null +++ b/packages/firebase_database/example/integration_test/firebase_database/query_e2e.dart @@ -0,0 +1,508 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:collection/collection.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void setupQueryTests() { + group('Query', () { + late DatabaseReference ref; + + setUp(() async { + ref = FirebaseDatabase.instance.ref('tests'); + + // Wipe the database before each test + await ref.remove(); + }); + + group('startAt', () { + test('returns null when no order modifier is applied', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + }); + + final snapshot = await ref.startAt(2).get(); + expect(snapshot.value, isNull); + }); + + test('starts at the correct value', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + }); + + final snapshot = await ref.orderByValue().startAt(2).get(); + + final expected = ['b', 'c', 'd']; + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('startAfter', () { + test('returns null when no order modifier is applied', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + }); + + final snapshot = await ref.startAfter(2).get(); + expect(snapshot.value, isNull); + }); + + test('starts after the correct value', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + }); + + // TODO(ehesp): Using `get` returns the wrong results. Have flagged with SDK team. + final e = await ref.orderByValue().startAfter(2).once(); + + final expected = ['c', 'd']; + + expect(e.snapshot.children.length, expected.length); + e.snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }, skip: "'startAfter' isn't supported by C++ SDK"); + + group('endAt', () { + test('returns all values when no order modifier is applied', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + }); + + final expected = ['a', 'b', 'c']; + + final snapshot = await ref.endAt(2).get(); + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + + test('ends at the correct value', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + }); + + final snapshot = await ref.orderByValue().endAt(2).get(); + + final expected = ['a', 'b']; + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('endBefore', () { + test('returns all values when no order modifier is applied', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + }); + + final expected = ['a', 'b', 'c']; + + final snapshot = await ref.endBefore(2).get(); + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + + test('ends before the correct value', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + }); + + final snapshot = await ref.orderByValue().endBefore(2).get(); + + final expected = ['a']; + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }, skip: "'endBefore' isn't supported by C++ SDK"); + + group('equalTo', () { + test('returns null when no order modifier is applied', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + }); + + final snapshot = await ref.equalTo(2).get(); + expect(snapshot.value, isNull); + }); + + test('returns the correct value', () async { + await ref.set({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + 'e': 2, + }); + + final snapshot = await ref.orderByValue().equalTo(2).get(); + + final expected = ['b', 'e']; + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('limitToFirst', () { + test('returns a limited array', () async { + await ref.set({ + 0: 'foo', + 1: 'bar', + 2: 'baz', + }); + + final snapshot = await ref.limitToFirst(2).get(); + + final expected = ['foo', 'bar']; + expect(snapshot.value, equals(expected)); + }); + + test('returns a limited object', () async { + await ref.set({ + 'a': 'foo', + 'b': 'bar', + 'c': 'baz', + }); + + final snapshot = await ref.limitToFirst(2).get(); + + final expected = { + 'a': 'foo', + 'b': 'bar', + }; + + expect(snapshot.value, equals(expected)); + }); + + test('returns null when no limit is possible', () async { + await ref.set('foo'); + + final snapshot = await ref.limitToFirst(2).get(); + + expect(snapshot.value, isNull); + }); + }); + + group('limitToLast', () { + test('returns a limited array', () async { + await ref.set({ + 0: 'foo', + 1: 'bar', + 2: 'baz', + }); + + final snapshot = await ref.limitToLast(2).get(); + + final expected = [null, 'bar', 'baz']; + expect(snapshot.value, equals(expected)); + }); + + test('returns a limited object', () async { + await ref.set({ + 'a': 'foo', + 'b': 'bar', + 'c': 'baz', + }); + + final snapshot = await ref.limitToLast(2).get(); + + final expected = { + 'b': 'bar', + 'c': 'baz', + }; + + expect(snapshot.value, equals(expected)); + }); + + test('returns null when no limit is possible', () async { + await ref.set('foo'); + + final snapshot = await ref.limitToLast(2).get(); + + expect(snapshot.value, isNull); + }); + }); + + group('orderByChild', () { + test('orders by a child value', () async { + await ref.set({ + 'a': { + 'string': 'foo', + 'number': 10, + }, + 'b': { + 'string': 'bar', + 'number': 5, + }, + 'c': { + 'string': 'baz', + 'number': 8, + }, + }); + + final snapshot = await ref.orderByChild('number').get(); + + final expected = ['b', 'c', 'a']; + expect(snapshot.children.length, equals(expected.length)); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('orderByKey', () { + test('orders by a key', () async { + await ref.set({ + 'b': { + 'string': 'bar', + 'number': 5, + }, + 'a': { + 'string': 'foo', + 'number': 10, + }, + 'c': { + 'string': 'baz', + 'number': 8, + }, + }); + + final snapshot = await ref.orderByKey().get(); + + final expected = ['a', 'b', 'c']; + + expect(snapshot.children.length, expected.length); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('orderByPriority', () { + test('orders by priority', () async { + await ref.set({ + 'a': { + 'string': 'foo', + 'number': 10, + }, + 'b': { + 'string': 'bar', + 'number': 5, + }, + 'c': { + 'string': 'baz', + 'number': 8, + }, + }); + + await Future.wait([ + ref.child('a').setPriority(2), + ref.child('b').setPriority(3), + ref.child('c').setPriority(1), + ]); + + final snapshot = await ref.orderByPriority().get(); + + final expected = ['c', 'a', 'b']; + expect(snapshot.children.length, equals(expected.length)); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('orderByValue', () { + test('orders by a value', () async { + await ref.set({ + 'a': 2, + 'b': 3, + 'c': 1, + }); + + await Future.wait([ + ref.child('a').setPriority(2), + ref.child('b').setPriority(3), + ref.child('c').setPriority(1), + ]); + + final snapshot = await ref.orderByValue().get(); + + final expected = ['c', 'a', 'b']; + expect(snapshot.children.length, equals(expected.length)); + snapshot.children.toList().forEachIndexed((i, childSnapshot) { + expect(childSnapshot.key, expected[i]); + }); + }); + }); + + group('onChildAdded', () { + test( + 'emits an event when a child is added', + () async { + expect( + ref.onChildAdded, + emitsInOrder([ + isA() + .having((s) => s.snapshot.value, 'value', 'foo') + .having((e) => e.type, 'type', DatabaseEventType.childAdded), + isA() + .having((s) => s.snapshot.value, 'value', 'bar') + .having((e) => e.type, 'type', DatabaseEventType.childAdded), + ]), + ); + + // NOTE(daeyeon): The event listener must be registered before the + // following operations take place. Similar issues can be found in + // other event-related TCs and they are addressed by one second delay. + await Future.delayed(const Duration(seconds: 1)); + + await ref.child('foo').set('foo'); + await ref.child('bar').set('bar'); + }, + retry: 2, + ); + }); + + group('onChildRemoved', () { + test( + 'emits an event when a child is removed', + () async { + await ref.child('foo').set('foo'); + await ref.child('bar').set('bar'); + + expect( + ref.onChildRemoved, + emitsInOrder([ + isA() + .having((s) => s.snapshot.value, 'value', 'bar') + .having( + (e) => e.type, + 'type', + DatabaseEventType.childRemoved, + ), + ]), + ); + // Give time for listen to be registered on native. + // TODO is there a better way to do this? + await Future.delayed(const Duration(seconds: 1)); + await ref.child('bar').remove(); + }, + retry: 2, + ); + }); + + group('onChildChanged', () { + test( + 'emits an event when a child is changed', + () async { + await ref.child('foo').set('foo'); + await ref.child('bar').set('bar'); + + expect( + ref.onChildChanged, + emitsInOrder([ + isA() + .having((s) => s.snapshot.key, 'key', 'bar') + .having((s) => s.snapshot.value, 'value', 'baz') + .having( + (e) => e.type, + 'type', + DatabaseEventType.childChanged, + ), + isA() + .having((s) => s.snapshot.key, 'key', 'foo') + .having((s) => s.snapshot.value, 'value', 'bar') + .having( + (e) => e.type, + 'type', + DatabaseEventType.childChanged, + ), + ]), + ); + // Give time for listen to be registered on native. + // TODO is there a better way to do this? + await Future.delayed(const Duration(seconds: 1)); + await ref.child('bar').set('baz'); + await ref.child('foo').set('bar'); + }, + retry: 2, + ); + }); + + group('onChildMoved', () { + test( + 'emits an event when a child is moved', + () async { + await ref.set({ + 'alex': {'nuggets': 60}, + 'rob': {'nuggets': 56}, + 'vassili': {'nuggets': 55.5}, + 'tony': {'nuggets': 52}, + 'greg': {'nuggets': 52}, + }); + + expect( + ref.orderByChild('nuggets').onChildMoved, + emitsInOrder([ + isA().having((s) => s.snapshot.value, 'value', { + 'nuggets': 57 + }).having((e) => e.type, 'type', DatabaseEventType.childMoved), + isA().having((s) => s.snapshot.value, 'value', { + 'nuggets': 61 + }).having((e) => e.type, 'type', DatabaseEventType.childMoved), + ]), + ); + // Give time for listen to be registered on native. + // TODO is there a better way to do this? + await Future.delayed(const Duration(seconds: 1)); + await ref.child('greg/nuggets').set(57); + await ref.child('rob/nuggets').set(61); + }, + retry: 2, + ); + }); + }); +} diff --git a/packages/firebase_database/example/lib/firebase_options.dart b/packages/firebase_database/example/lib/firebase_options.dart new file mode 100644 index 0000000..fb3c146 --- /dev/null +++ b/packages/firebase_database/example/lib/firebase_options.dart @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, TargetPlatform; + +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + switch (defaultTargetPlatform) { + case TargetPlatform.linux: + // Note: To find out if you are using the Tizen platform, refer to the link below. + // https://github.com/flutter-tizen/flutter-tizen/issues/482#issuecomment-1441139704 + return tizen; + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static String get emulatorHost { + return 'PLACEHOLDER'; + } + + static const FirebaseOptions tizen = FirebaseOptions( + apiKey: 'PLACEHOLDER', + appId: 'PLACEHOLDER', + messagingSenderId: 'PLACEHOLDER', + projectId: 'PLACEHOLDER', + databaseURL: 'PLACEHOLDER', + storageBucket: 'PLACEHOLDER', + ); +} diff --git a/packages/firebase_database/example/lib/main.dart b/packages/firebase_database/example/lib/main.dart new file mode 100644 index 0000000..ebadd96 --- /dev/null +++ b/packages/firebase_database/example/lib/main.dart @@ -0,0 +1,140 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'firebase_options.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () async { + // Running these APIs manually as they're failing on CI due to required keychain sharing entitlements + // See this issue https://github.com/firebase/flutterfire/issues/9538 + // You will also need to add the keychain sharing entitlements to this test app and sign code with development team for app & tests to successfully run + if (Platform.isMacOS && kDebugMode) { + // ignore_for_file: avoid_print + // Wait a little so we don't get a delete-pending exception + await Future.delayed(const Duration(seconds: 8)); + } + }, + child: const Text('Test macOS tests manually'), + ), + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/packages/firebase_database/example/pubspec.yaml b/packages/firebase_database/example/pubspec.yaml new file mode 100644 index 0000000..4186081 --- /dev/null +++ b/packages/firebase_database/example/pubspec.yaml @@ -0,0 +1,29 @@ +name: tests +description: A test app for firebase database plugin (e2e). + +environment: + sdk: ">=2.18.0 <4.0.0" + +dependencies: + firebase_core: 2.4.1 + firebase_core_tizen: ^1.0.0 + firebase_database: 10.0.9 + firebase_database_tizen: ^0.1.0 + flutter: + sdk: flutter + +dependency_overrides: + firebase_core_tizen: + path: ../../firebase_core + firebase_database_tizen: + path: .. + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + integration_test_tizen: ^2.0.1 + +flutter: + uses-material-design: true diff --git a/packages/firebase_database/example/test_driver/integration_test.dart b/packages/firebase_database/example/test_driver/integration_test.dart new file mode 100644 index 0000000..f1ac26f --- /dev/null +++ b/packages/firebase_database/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2022, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/firebase_database/example/tizen/.gitignore b/packages/firebase_database/example/tizen/.gitignore new file mode 100644 index 0000000..750f3af --- /dev/null +++ b/packages/firebase_database/example/tizen/.gitignore @@ -0,0 +1,5 @@ +flutter/ +.vs/ +*.user +bin/ +obj/ diff --git a/packages/firebase_database/example/tizen/App.cs b/packages/firebase_database/example/tizen/App.cs new file mode 100644 index 0000000..6dd4a63 --- /dev/null +++ b/packages/firebase_database/example/tizen/App.cs @@ -0,0 +1,20 @@ +using Tizen.Flutter.Embedding; + +namespace Runner +{ + public class App : FlutterApplication + { + protected override void OnCreate() + { + base.OnCreate(); + + GeneratedPluginRegistrant.RegisterPlugins(this); + } + + static void Main(string[] args) + { + var app = new App(); + app.Run(args); + } + } +} diff --git a/packages/firebase_database/example/tizen/Runner.csproj b/packages/firebase_database/example/tizen/Runner.csproj new file mode 100644 index 0000000..f4e369d --- /dev/null +++ b/packages/firebase_database/example/tizen/Runner.csproj @@ -0,0 +1,19 @@ + + + + Exe + tizen40 + + + + + + + + + + %(RecursiveDir) + + + + diff --git a/packages/firebase_database/example/tizen/shared/res/ic_launcher.png b/packages/firebase_database/example/tizen/shared/res/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/firebase_database/example/tizen/tizen-manifest.xml b/packages/firebase_database/example/tizen/tizen-manifest.xml new file mode 100644 index 0000000..b912296 --- /dev/null +++ b/packages/firebase_database/example/tizen/tizen-manifest.xml @@ -0,0 +1,16 @@ + + + + + + ic_launcher.png + + + + T-INFOLINK2021-1000 + + + http://tizen.org/privilege/internet + + + diff --git a/packages/firebase_database/pubspec.yaml b/packages/firebase_database/pubspec.yaml new file mode 100644 index 0000000..9770425 --- /dev/null +++ b/packages/firebase_database/pubspec.yaml @@ -0,0 +1,33 @@ +name: firebase_database_tizen +description: Flutter plugin for Firebase Database, a cloud-hosted NoSQL database + with realtime data syncing across Android and iOS clients, and offline access. +version: 0.1.0 +publish_to: 'none' + +environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + firebase_core_tizen: ^1.0.0 + firebase_database: 10.0.9 + firebase_database_platform_interface: 0.2.2+17 + flutter: + sdk: flutter + +dependency_overrides: + firebase_core_tizen: + path: ../firebase_core + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + implements: firebase_database + platforms: + tizen: + pluginClass: FirebaseDatabaseTizenPlugin + fileName: firebase_database_tizen_plugin.h diff --git a/packages/firebase_database/tizen/.gitignore b/packages/firebase_database/tizen/.gitignore new file mode 100644 index 0000000..a2a7d62 --- /dev/null +++ b/packages/firebase_database/tizen/.gitignore @@ -0,0 +1,5 @@ +.cproject +.sign +crash-info/ +Debug/ +Release/ diff --git a/packages/firebase_database/tizen/build_def.prop b/packages/firebase_database/tizen/build_def.prop new file mode 100644 index 0000000..64d7f61 --- /dev/null +++ b/packages/firebase_database/tizen/build_def.prop @@ -0,0 +1,2 @@ +PREBUILD_COMMAND = ./tar_url.sh \ + https://github.com/daeye0n/gooddaytocode/archive/refs/tags/v10.4.0-draft-4.tar.gz diff --git a/packages/firebase_database/tizen/dep/conversion.cc b/packages/firebase_database/tizen/dep/conversion.cc new file mode 100644 index 0000000..44d0035 --- /dev/null +++ b/packages/firebase_database/tizen/dep/conversion.cc @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/conversion.h" + +#include + +#include "common/trace.h" // for UNIMPLEMENTED and FATAL +#include "common/utils.h" + +using firebase::Variant; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +Variant Conversion::ToFirebaseVariant(const EncodableValue& encodable_value) { + switch (encodable_value.index()) { + case 0: // std::monostate + return Variant(); + case 1: // bool + return Variant(std::get(encodable_value)); + case 2: // int32_t + return Variant(std::get(encodable_value)); + case 3: // int64_t + return Variant(std::get(encodable_value)); + case 4: // double + return Variant(std::get(encodable_value)); + case 5: // std::string + return Variant(std::get(encodable_value)); + case 6: // std::vector + return Variant(std::get>(encodable_value)); + case 7: // std::vector + return Variant(std::get>(encodable_value)); + case 8: // std::vector + return Variant(std::get>(encodable_value)); + case 9: // std::vector + return Variant(std::get>(encodable_value)); + case 10: // EncodableList + return Conversion::ToFirebaseVariant( + std::get(encodable_value)); + case 11: // EncodableMap + return Conversion::ToFirebaseVariant( + std::get(encodable_value)); + case 12: // CustomEncodableValue + UNIMPLEMENTED("Unknown to handle this"); + return Variant(); + case 13: // std::vector + return Variant(std::get>(encodable_value)); + default: + FATAL("Invalid EncodableValue type"); + } + return Variant(); +} + +Variant Conversion::ToFirebaseVariant(const EncodableList& encodable_list) { + std::vector variant_list; + for (const EncodableValue& encodable_value : encodable_list) { + variant_list.push_back(Conversion::ToFirebaseVariant(encodable_value)); + } + return Variant(variant_list); +} + +Variant Conversion::ToFirebaseVariant(const EncodableMap& encodable_map) { + std::map variant_map; + for (const auto& [key, value] : encodable_map) { + variant_map.emplace(Conversion::ToFirebaseVariant(key), + Conversion::ToFirebaseVariant(value)); + } + return Variant(variant_map); +} + +Variant Conversion::ToFirebaseVariant(const EncodableMap* map, + const char* key) { + return Conversion::ToFirebaseVariant(GetEncodableValue(map, key)); +} + +EncodableValue Conversion::ToEncodableValue(const Variant& v) { + switch (v.type()) { + case Variant::kTypeNull: + return EncodableValue(std::monostate()); + case Variant::kTypeInt64: + return EncodableValue(v.int64_value()); + case Variant::kTypeDouble: + return EncodableValue(v.double_value()); + case Variant::kTypeBool: + return EncodableValue(v.bool_value()); + case Variant::kTypeStaticString: + return EncodableValue(std::string(v.string_value())); + case Variant::kTypeMutableString: + return EncodableValue(v.mutable_string()); + case Variant::kTypeVector: { + EncodableList list; + for (const auto& e : v.vector()) { + list.push_back(ToEncodableValue(e)); + } + return EncodableValue(list); + } + case Variant::kTypeMap: { + EncodableMap map; + for (const auto& [key, value] : v.map()) { + map[ToEncodableValue(key)] = ToEncodableValue(value); + } + return EncodableValue(map); + } + default: + FATAL("Unsupported Variant type"); + } + return EncodableValue(); +} diff --git a/packages/firebase_database/tizen/dep/include/common/conversion.h b/packages/firebase_database/tizen/dep/include/common/conversion.h new file mode 100644 index 0000000..10a2507 --- /dev/null +++ b/packages/firebase_database/tizen/dep/include/common/conversion.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +class Conversion { + public: + static firebase::Variant ToFirebaseVariant(const flutter::EncodableValue& v); + static firebase::Variant ToFirebaseVariant(const flutter::EncodableList& l); + static firebase::Variant ToFirebaseVariant(const flutter::EncodableMap& m); + static firebase::Variant ToFirebaseVariant(const flutter::EncodableMap* m, + const char* key); + static flutter::EncodableValue ToEncodableValue(const firebase::Variant& v); +}; diff --git a/packages/firebase_database/tizen/dep/include/common/logger.h b/packages/firebase_database/tizen/dep/include/common/logger.h new file mode 100644 index 0000000..95d6b62 --- /dev/null +++ b/packages/firebase_database/tizen/dep/include/common/logger.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2022-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +class LogOption { + public: + static bool isEnabled(const std::string& pattern = ""); + static void setExternalIsEnabled(std::function); + + private: + static std::function externalIsEnabled; +}; + +class Logger { + public: + class Header { + protected: + virtual void writeHeader(std::stringstream&) = 0; + + private: + void write(std::stringstream& stream); + friend class Logger; + }; + + class Output { + public: + virtual ~Output() = default; + virtual void flush(std::stringstream& ss) = 0; + }; + + Logger(std::shared_ptr out = nullptr); + Logger(const std::string& header, std::shared_ptr out = nullptr); + Logger(Header&& header, std::shared_ptr out = nullptr); + virtual ~Logger(); + + template + Logger& operator<<(const T& msg) { + if (!isEnabled()) { + return *this; + } + stream_ << msg; + return *this; + } + + template + Logger& log(const T& v, TArgs... args) { + if (!isEnabled()) { + return *this; + } + stream_ << v << " "; + log(args...); + return *this; + } + template + Logger& log(const T& v) { + if (!isEnabled()) { + return *this; + } + stream_ << v; + return *this; + } + Logger& log() { return *this; } + + template + Logger& print(const char* format, T value, Args... args) { + if (!isEnabled()) { + return *this; + } + + while (*format) { + if (*format == '%' && *(++format) != '%') { + stream_ << value; + + // handle sub-specifiers + if ((*format == 'z')) { + format++; + } else if ((*format == 'l') || (*format == 'h')) { + format++; + if (*format == *(format + 1)) { + format++; + } + } + format++; + + print(format, args...); + return *this; + } + stream_ << *format++; + } + assert(((void)"logical error: should not come here", false)); + return *this; + }; + + Logger& print(const char* string_without_format_specifiers = ""); + Logger& flush(); + bool isEnabled(const std::string& pattern = "") { + return LogOption::isEnabled(pattern); + } + + protected: + std::stringstream stream_; + void initialize(std::shared_ptr out = nullptr); + + private: + std::shared_ptr output_; +}; + +class StdOut : public Logger::Output { + public: + static std::shared_ptr instance() { + static std::shared_ptr output = std::make_shared(); + return output; + } + + void flush(std::stringstream& ss) override; +}; + +// --- Utils --- + +#ifndef __FILE_NAME__ +#define __FILE_NAME__ \ + (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +#define __FUNCTION_NAME__ getPrettyFunctionName(__PRETTY_FUNCTION__) + +#define __CODE_LOCATION__ \ + createCodeLocation(__PRETTY_FUNCTION__, __FILE_NAME__, __LINE__).c_str() + +std::string createCodeLocation(const char* functionName, const char* filename, + const int line, std::string prefixPattern = ""); + +std::string getPrettyFunctionName(const std::string& fullname, + std::string prefixPattern = ""); + +void writeThreadIdentifier(std::ostream& ss); + +class IndentCounter { + public: + IndentCounter(std::string id); + ~IndentCounter(); + static std::string getString(std::string id = ""); + static void indent(std::string id); + static void unIndent(std::string id); + + private: + std::string id_; +}; diff --git a/packages/firebase_database/tizen/dep/include/common/to_string.h b/packages/firebase_database/tizen/dep/include/common/to_string.h new file mode 100644 index 0000000..5871289 --- /dev/null +++ b/packages/firebase_database/tizen/dep/include/common/to_string.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include + +std::ostream& operator<<(std::ostream& os, const firebase::Variant& v); +std::ostream& operator<<(std::ostream& os, const flutter::EncodableValue& v); + +#ifdef FIREBASE_DATABASE +#include +#include + +std::ostream& operator<<(std::ostream& os, + const firebase::database::DataSnapshot& d); +std::ostream& operator<<(std::ostream& os, + const firebase::database::MutableData& d); +#endif // FIREBASE_DATABASE + +template +static std::ostream& operator<<(std::ostream& os, const std::vector& v) { + static size_t limit = std::numeric_limits::max(); + + size_t n = v.size(); + size_t end = std::min(n, limit); + os << "["; + for (size_t i = 0; i < end; i++) { + os << v[i]; + if (i < end - 1) { + os << ", "; + } + } + if (end < n) { + os << ", ..."; + } + os << "]"; + return os; +} + +template +static std::ostream& operator<<(std::ostream& os, const std::map& m) { + os << "{ "; + for (const auto& [key, value] : m) { + os << key << ": " << value << ", "; + } + os << "}"; + return os; +} + +template +std::string ToString(const T& value) { + std::stringstream ss; + ss << value; + return ss.str(); +} diff --git a/packages/firebase_database/tizen/dep/include/common/trace.h b/packages/firebase_database/tizen/dep/include/common/trace.h new file mode 100644 index 0000000..20077b9 --- /dev/null +++ b/packages/firebase_database/tizen/dep/include/common/trace.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either xess or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "logger.h" + +class Trace : public Logger { + public: + Trace(std::string id); + Trace(std::string id, const char* functionName, const char* filename, + const int line); + template + Trace(std::string id, const char* functionName, const char* filename, + const int line, const T& v, TArgs... args) + : Trace(id, functionName, filename, line) { + if (!isEnabled(id)) { + return; + } + stream_ << v << " "; + log(args...); + } + + class Option { + public: + static void setTag(const char* tagName) { tag_ = tagName; } + static const char* tag() { return tag_.c_str(); } + + private: + static std::string tag_; + }; +}; + +#if defined(NDEBUG) + +#define TRACE(id, ...) +#define TRACE0(id, ...) +#define TRACEF(id, ...) +#define TRACEF0(id, ...) +#define TRACE_SCOPE(id, ...) +#define TRACE_SCOPE0(id, ...) + +#else + +#define TRACE(id, ...) \ + Trace(#id, __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__).log(__VA_ARGS__) + +#define TRACE0(id, ...) Trace(#id).log(__VA_ARGS__) + +#define TRACEF(id, ...) \ + Trace(#id, __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__).print(__VA_ARGS__) + +#define TRACEF0(id, ...) Trace(#id).print(__VA_ARGS__) + +#define TRACE_SCOPE(id, ...) \ + IndentCounter __counter(#id); \ + TRACE(id, __VA_ARGS__) + +#define TRACE_SCOPE0(id, ...) \ + IndentCounter __counter(#id); \ + TRACE(id, __VA_ARGS__); \ + Trace __outter(#id, __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__, \ + "/" __VA_ARGS__) + +#endif + +#ifdef __GNUC__ +#define LIKELY(condition) __builtin_expect(!!(condition), 1) +#define UNLIKELY(condition) __builtin_expect(!!(condition), 0) +#else +#define LIKELY(condition) (condition) +#define UNLIKELY(condition) (condition) +#endif + +void Trace_Fatal(const char* functionName, const char* filename, const int line, + const std::string message); + +#define FATAL(...) \ + Trace_Fatal(__PRETTY_FUNCTION__, __FILE_NAME__, __LINE__, __VA_ARGS__) +#define CHECK_FAILED_HANDLER(message) FATAL("Check failed: " message) +#define CHECK_WITH_MSG(condition, message) \ + do { \ + if (UNLIKELY(!(condition))) { \ + CHECK_FAILED_HANDLER(message); \ + } \ + } while (false) +#define CHECK(condition) CHECK_WITH_MSG(condition, #condition) +#define CHECK_NULL(val) CHECK((val) == nullptr) +#define CHECK_NOT_NULL(val) CHECK((val) != nullptr) +#define UNIMPLEMENTED(...) CHECK_WITH_MSG(false, "[UNIMPLEMENTED] " __VA_ARGS__) diff --git a/packages/firebase_database/tizen/dep/include/common/utils.h b/packages/firebase_database/tizen/dep/include/common/utils.h new file mode 100644 index 0000000..647d5d2 --- /dev/null +++ b/packages/firebase_database/tizen/dep/include/common/utils.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include +#include +#include + +#include "trace.h" // UNLIKELY, FATAL + +template +class PointerScope { + public: + PointerScope() = delete; + PointerScope(void* p) { value_ = reinterpret_cast(p); } + virtual ~PointerScope() { delete value_; } + T* operator->() { return value_; } + + private: + T* value_; +}; + +template +class Optional : public std::optional { + public: + using std::optional::optional; + + template + T checked(F&& handler = {}) { + if (UNLIKELY(!this->has_value())) { + if constexpr (std::is_invocable_v) { + std::invoke(std::forward(handler)); + } else { + FATAL("Empty Optional"); + } + } + return this->value(); + } +}; + +// EncodableMap + +template +Optional GetOptionalValue(const flutter::EncodableMap* map, + const char* key) { + const auto& iter = map->find(flutter::EncodableValue(key)); + if (iter != map->end() && !iter->second.IsNull()) { + if (auto* value = std::get_if(&iter->second)) { + return *value; + } + } + return std::nullopt; +} + +flutter::EncodableValue GetEncodableValue(const flutter::EncodableMap* map, + const char* key); diff --git a/packages/firebase_database/tizen/dep/logger.cc b/packages/firebase_database/tizen/dep/logger.cc new file mode 100644 index 0000000..665317f --- /dev/null +++ b/packages/firebase_database/tizen/dep/logger.cc @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "include/common/logger.h" + +#include +#include +#include +#include + +// --- Formatter --- + +std::string getPrettyFunctionName(const std::string& fullname, + std::string prefixPattern) { + std::stringstream ss; + if (!prefixPattern.empty()) { + ss << "(?:" << prefixPattern << ")|"; + } + ss << R"((?::\()|([\w:~]+)\()"; + + try { + std::smatch match; + const std::regex re(ss.str()); + + std::stringstream result; + std::string suffix = fullname; + while (std::regex_search(suffix, match, re)) { + result << match[1]; + suffix = match.suffix(); + } + return result.str(); + } catch (std::regex_error& e) { + return ""; + } +} + +std::string createCodeLocation(const char* functionName, const char* filename, + const int line, std::string prefixPattern) { + std::ostringstream oss; + oss << getPrettyFunctionName(functionName, prefixPattern) << " (" << filename + << ":" << line << ")"; + return oss.str(); +} + +void writeThreadIdentifier(std::ostream& os) { + static int id = 0; + static thread_local int thisThreadId = 0; + + if (thisThreadId == 0) { + thisThreadId = ++id; + } + os << "[" << thisThreadId << "] "; +} + +std::function LogOption::externalIsEnabled; + +void LogOption::setExternalIsEnabled( + std::function func) { + externalIsEnabled = func; +} + +// --- LogOption --- + +bool LogOption::isEnabled(const std::string& pattern) { + if (externalIsEnabled == nullptr) { + return false; + } + return externalIsEnabled(pattern); +} + +// --- Logger::Header --- + +void Logger::Header::write(std::stringstream& stream) { + writeThreadIdentifier(stream); + writeHeader(stream); +} + +// --- Logger --- + +Logger::Logger(std::shared_ptr out) { initialize(out); } + +Logger::Logger(const std::string& header, std::shared_ptr out) + : output_(out) { + initialize(output_); + stream_ << header; +} + +Logger::Logger(Header&& header, std::shared_ptr out) : output_(out) { + initialize(output_); + header.write(stream_); +} + +Logger::~Logger() { + if (!isEnabled() || output_ == nullptr) { + return; + } + // stream ends with both reset-styles and endl characters. + stream_ << "\033[0m" << std::endl; + output_->flush(stream_); +} + +void Logger::initialize(std::shared_ptr out) { + static thread_local std::shared_ptr loggerOutput; + + if (out == nullptr) { + if (loggerOutput == nullptr) { + loggerOutput = std::make_shared(); + } + output_ = loggerOutput; + } else { + output_ = out; + } +} + +Logger& Logger::print(const char* string_without_format_specifiers) { + if (output_ == nullptr) { + return *this; + } + + while (*string_without_format_specifiers) { + if (*string_without_format_specifiers == '%' && + *(++string_without_format_specifiers) != '%') { + assert(((void)"runtime error: invalid format-string", false)); + } + stream_ << *string_without_format_specifiers++; + } + return *this; +} + +Logger& Logger::flush() { + if (isEnabled() && output_) { + output_->flush(stream_); + } + stream_.str(""); + return *this; +} +// --- Output --- + +void StdOut::flush(std::stringstream& stream) { std::cout << stream.str(); } + +// --- Utils --- + +thread_local int indentCount = 0; +thread_local int deltaCount = 0; + +void IndentCounter::indent(std::string id) { + if (!LogOption::isEnabled(id)) { + return; + } + deltaCount++; +} + +void IndentCounter::unIndent(std::string id) { + if (!LogOption::isEnabled(id)) { + return; + } + deltaCount--; +} + +IndentCounter::IndentCounter(std::string id) { + id_ = id; + + if (!LogOption::isEnabled(id)) { + return; + } + indentCount++; +} + +IndentCounter::~IndentCounter() { + if (!LogOption::isEnabled(id_)) { + return; + } + indentCount--; +} + +std::string IndentCounter::getString(std::string id) { + assert(indentCount >= 0); + + std::ostringstream oss; + int count = indentCount + deltaCount; + + if (deltaCount > 0) { + oss << deltaCount << " "; + } + + for (int i = 1; i < std::min(30, count); ++i) { + oss << " "; + } + + return oss.str(); +} diff --git a/packages/firebase_database/tizen/dep/to_string.cc b/packages/firebase_database/tizen/dep/to_string.cc new file mode 100644 index 0000000..7591227 --- /dev/null +++ b/packages/firebase_database/tizen/dep/to_string.cc @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "include/common/to_string.h" + +using firebase::Variant; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +std::ostream& operator<<(std::ostream& os, const Variant& v) { + switch (v.type()) { + case Variant::kTypeNull: + os << "null"; + break; + case Variant::kTypeInt64: + os << v.int64_value(); + break; + case Variant::kTypeDouble: + os << v.double_value(); + break; + case Variant::kTypeBool: + os << std::boolalpha << v.bool_value() << std::noboolalpha; + break; + case Variant::kTypeStaticString: + os << v.string_value(); + break; + case Variant::kTypeMutableString: + os << v.mutable_string(); + break; + case Variant::kTypeVector: + os << v.vector(); + break; + case Variant::kTypeMap: + os << v.map(); + break; + default: + os << ""; + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const EncodableValue& v) { + switch (v.index()) { + case 0: // std::monostate + os << "null"; + break; + case 1: // bool + os << std::boolalpha << std::get(v) << std::noboolalpha; + break; + case 2: // int32_t + os << std::get(v); + break; + case 3: // int64_t + os << std::get(v); + break; + case 4: // double + os << std::get(v); + break; + case 5: // std::string + os << "\"" << std::get(v) << "\""; + break; + case 6: // std::vector + os << std::get>(v); + break; + case 7: // std::vector + os << std::get>(v); + break; + case 8: // std::vector + os << std::get>(v); + break; + case 9: // std::vector + os << std::get>(v); + break; + case 10: { // EncodableList + os << std::get(v); + break; + } + case 11: { // EncodableMap + os << std::get(v); + break; + } + case 12: // CustomEncodableValue + os << ""; + break; + case 13: // std::vector + os << std::get>(v); + break; + default: + os << ""; + break; + } + return os; +} + +#ifdef FIREBASE_DATABASE + +using firebase::database::DataSnapshot; +using firebase::database::MutableData; + +std::ostream& operator<<(std::ostream& os, const DataSnapshot& snapshot) { + static thread_local std::string indent; + + os << indent << "{\n"; + indent += " "; + + os << indent << "key: " << snapshot.key_string() << ", \n" + << indent << "value: " << snapshot.value() << ", \n" + << indent << "priority: " << snapshot.priority() << ", \n" + << indent << "children_count: " << snapshot.children_count() << ", \n"; + + for (const auto& s : snapshot.children()) { + os << s; + } + + indent = indent.substr(0, indent.length() - 2); + os << indent << "},\n"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const MutableData& data) { + static thread_local std::string indent; + + os << indent << "{\n"; + indent += " "; + + // Functions like children_count() or priority() is not marked as const. This + // seems to be a firebase mistake. So we convert it to a const reference. + MutableData& mutable_data = const_cast(data); + + os << indent << "key: " << mutable_data.key_string() << ", \n" + << indent << "value: " << mutable_data.value() << ", \n" + << indent << "priority: " << mutable_data.priority() << ", \n" + << indent << "children_count: " << mutable_data.children_count() << ", \n"; + + for (auto& d : mutable_data.children()) { + os << d; + } + + indent = indent.substr(0, indent.length() - 2); + os << indent << "},\n"; + return os; +} + +#endif // FIREBASE_DATABASE diff --git a/packages/firebase_database/tizen/dep/trace.cc b/packages/firebase_database/tizen/dep/trace.cc new file mode 100644 index 0000000..a452bf7 --- /dev/null +++ b/packages/firebase_database/tizen/dep/trace.cc @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "common/trace.h" + +#include // assert +#include // setfill and setw + +#define TYPE_LENGTH_LIMIT 5 +#define TRACE_ID_LENGTH_LIMIT 10 +#define COLOR_RESET "\033[0m" +#define COLOR_DIM "\033[0;2m" +#define LOG_PREFIX_PATTERN ".*Plugin" + +std::string Trace::Option::tag_ = "FirebasePlugin"; + +#if defined(__TIZEN__) || defined(TIZEN) + +#include + +class PriorityLog { + public: + class Debug : public Logger::Output { + public: + void flush(std::stringstream& stream) override { + dlog_print(DLOG_DEBUG, Trace::Option::tag(), "%s", stream.str().c_str()); + } + }; + + class Warn : public Logger::Output { + public: + void flush(std::stringstream& stream) override { + dlog_print(DLOG_WARN, Trace::Option::tag(), "%s", stream.str().c_str()); + } + }; + + class Error : public Logger::Output { + public: + void flush(std::stringstream& stream) override { + dlog_print(DLOG_ERROR, Trace::Option::tag(), "%s", stream.str().c_str()); + } + }; +}; + +class CustomOutput : public PriorityLog::Debug { + public: + static std::shared_ptr instance() { + static std::shared_ptr output = + std::make_shared(); + return output; + } +}; + +// -- Fatal -- +static std::string trim(std::string const& str, + std::string const& whitespace = " \r\n\t\v\f") { + if (str.length() == 0) return ""; + std::size_t start = str.find_first_not_of(whitespace); + std::size_t end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); +} + +void Trace_Fatal(const char* functionName, const char* filename, const int line, + const std::string message) { + std::stringstream stream; + if (message.length() > 0) stream << trim(message) << " "; + stream << createCodeLocation(functionName, filename, line); + PriorityLog::Error().flush(stream); + assert(false); +} + +#else + +class CustomOutput : public StdOut::Output { + public: + static std::shared_ptr instance() { return StdOut::instance(); } +}; +#endif + +static void writeHeader(std::ostream& ss, const std::string& tag, + const std::string& id) { + ss << COLOR_DIM; + ss << std::left << std::setfill(' ') << "(" + << std::setw(TRACE_ID_LENGTH_LIMIT) + << std::string(id).substr(0, TRACE_ID_LENGTH_LIMIT) << ") "; +} + +Trace::Trace(std::string id, const char* functionName, const char* filename, + const int line) + : Logger(CustomOutput::instance()) { + if (!isEnabled(id)) { + return; + } + + writeHeader(stream_, Option::tag(), id); + stream_ << IndentCounter::getString(id) + << createCodeLocation(functionName, filename, line, + LOG_PREFIX_PATTERN) + << " " << COLOR_RESET; +} + +Trace::Trace(std::string id) : Logger(CustomOutput::instance()) { + if (!isEnabled(id)) { + return; + } + + writeHeader(stream_, Option::tag(), id); +} diff --git a/packages/firebase_database/tizen/dep/utils.cc b/packages/firebase_database/tizen/dep/utils.cc new file mode 100644 index 0000000..280358c --- /dev/null +++ b/packages/firebase_database/tizen/dep/utils.cc @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/utils.h" + +using flutter::EncodableMap; +using flutter::EncodableValue; + +EncodableValue GetEncodableValue(const EncodableMap* map, const char* key) { + const auto& iter = map->find(EncodableValue(key)); + if (iter != map->end()) { + return iter->second; + } + // EncodableMap has no key. + return EncodableValue(); +} diff --git a/packages/firebase_database/tizen/inc/firebase_database_tizen_plugin.h b/packages/firebase_database/tizen/inc/firebase_database_tizen_plugin.h new file mode 100644 index 0000000..b57a328 --- /dev/null +++ b/packages/firebase_database/tizen/inc/firebase_database_tizen_plugin.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_FIREBASE_DATABASE_TIZEN_PLUGIN_H_ +#define FLUTTER_PLUGIN_FIREBASE_DATABASE_TIZEN_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void FirebaseDatabaseTizenPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_FIREBASE_DATABASE_TIZEN_PLUGIN_H_ diff --git a/packages/firebase_database/tizen/project_def.prop b/packages/firebase_database/tizen/project_def.prop new file mode 100644 index 0000000..b67ee97 --- /dev/null +++ b/packages/firebase_database/tizen/project_def.prop @@ -0,0 +1,30 @@ +# See https://docs.tizen.org/application/tizen-studio/native-tools/project-conversion +# for details. + +APPNAME = firebase_database_tizen_plugin +type = sharedLib +profile = common-7.0 + +# Source files +USER_SRCS += src/*.cc dep/*.cc + +# User defines +USER_DEFS = +USER_UNDEFS = +USER_CPP_DEFS = FLUTTER_PLUGIN_IMPL TIZEN __TIZEN__ FIREBASE_DATABASE +USER_CPP_UNDEFS = + +# Custom defines +FIREBASE_SDK_DIR = $(subst $() ,\ ,$(FLUTTER_BUILD_DIR))/.firebaseSDK +FIREBASE_INC_DIR = $(FIREBASE_SDK_DIR)/inc +FIREBASE_LIB_DIR = $(FIREBASE_SDK_DIR)/lib/$(BUILD_ARCH) + +# User includes +USER_INC_DIRS = inc src dep/include $(FIREBASE_INC_DIR) +USER_INC_FILES = +USER_CPP_INC_FILES = + +# Linker options +USER_LIBS = firebase_app firebase_database +USER_LIB_DIRS = lib/$(BUILD_ARCH) $(FIREBASE_LIB_DIR) +USER_LFLAGS = -Wl,-rpath='$$ORIGIN' diff --git a/packages/firebase_database/tizen/src/constants.h b/packages/firebase_database/tizen/src/constants.h new file mode 100644 index 0000000..daca506 --- /dev/null +++ b/packages/firebase_database/tizen/src/constants.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +class Constants { + public: + static constexpr char kAborted[] = "aborted"; + static constexpr char kAppName[] = "appName"; + static constexpr char kChildKeys[] = "childKeys"; + static constexpr char kChildAdded[] = "childAdded"; + static constexpr char kChildRemove[] = "childRemoved"; + static constexpr char kChildChanged[] = "childChanged"; + static constexpr char kChildMoved[] = "childMoved"; + static constexpr char kCommitted[] = "committed"; + static constexpr char kCursor[] = "cursor"; + static constexpr char kDatabaseCacheSizeBytes[] = "cacheSizeBytes"; + static constexpr char kDatabaseEmulatorHost[] = "emulatorHost"; + static constexpr char kDatabaseEmulatorPort[] = "emulatorPort"; + static constexpr char kDatabaseLoggingEnabled[] = "loggingEnabled"; + static constexpr char kDatabasePersistenceEnabled[] = "persistenceEnabled"; + static constexpr char kDatabaseURL[] = "databaseURL"; + static constexpr char kDefalutAppName[] = "[DEFAULT]"; + static constexpr char kEndAt[] = "endAt"; + static constexpr char kEndBefore[] = "endBefore"; + static constexpr char kEventChannelNamePrefix[] = "eventChannelNamePrefix"; + static constexpr char kEventType[] = "eventType"; + static constexpr char kException[] = "exception"; + static constexpr char kKey[] = "key"; + static constexpr char kLimit[] = "limit"; + static constexpr char kLimitToFirst[] = "limitToFirst"; + static constexpr char kLimitToLast[] = "limitToLast"; + static constexpr char kModifiers[] = "modifiers"; + static constexpr char kName[] = "name"; + static constexpr char kOrderBy[] = "orderBy"; + static constexpr char kOrderByChild[] = "orderByChild"; + static constexpr char kOrderByKey[] = "orderByKey"; + static constexpr char kOrderByPriority[] = "orderByPriority"; + static constexpr char kOrderByValue[] = "orderByValue"; + static constexpr char kPath[] = "path"; + static constexpr char kPreviousChildKey[] = "previousChildKey"; + static constexpr char kPriority[] = "priority"; + static constexpr char kSnapshot[] = "snapshot"; + static constexpr char kStartAfter[] = "startAfter"; + static constexpr char kStartAt[] = "startAt"; + static constexpr char kTransactionApplyLocally[] = "transactionApplyLocally"; + static constexpr char kTransactionKey[] = "transactionKey"; + static constexpr char kType[] = "type"; + static constexpr char kValue[] = "value"; +}; diff --git a/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc new file mode 100644 index 0000000..9660539 --- /dev/null +++ b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc @@ -0,0 +1,677 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "firebase_database_tizen_plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/conversion.h" +#include "common/to_string.h" +#include "common/trace.h" +#include "common/utils.h" +#include "constants.h" +#include "firebase_database_utils.h" + +using firebase::Future; +using firebase::FutureStatus; +using firebase::Variant; +using firebase::database::ChildListener; +using firebase::database::DatabaseReference; +using firebase::database::DataSnapshot; +using firebase::database::Error; +using firebase::database::MutableData; +using firebase::database::Query; +using firebase::database::TransactionResult; +using flutter::BinaryMessenger; +using flutter::EncodableMap; +using flutter::EncodableValue; +using flutter::EventChannel; +using flutter::EventSink; +using flutter::MethodChannel; +using flutter::MethodResult; +using flutter::StreamHandler; +using flutter::StreamHandlerError; + +using EncodableValuePair = std::pair; + +namespace { + +static constexpr char kMethodChannelName[] = + "plugins.flutter.io/firebase_database"; + +class FirebaseDatabaseTizenPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar) { + auto plugin = std::make_unique(); + + plugin->channel_ = std::make_unique>( + registrar->messenger(), kMethodChannelName, + &flutter::StandardMethodCodec::GetInstance()); + + plugin->binary_messenger_ = registrar->messenger(); + + plugin->channel_->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto& call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); + } + + private: + void HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) { + const std::string& method_name = method_call.method_name(); + const auto* arguments = std::get_if(method_call.arguments()); + if (!arguments) { + return result->Error("Invalid arguments", "Invalid argument type."); + } + + TRACEF(DATABASE, "[TIZEN: HANDLE_METHOD_CALL] %s {\n%s\n}", method_name, + ToString(*arguments)); + +#define DATABASE_METHODS(V) \ + V("FirebaseDatabase#goOnline", DatabaseGoOnline) \ + V("FirebaseDatabase#goOffline", DatabaseGoOffline) \ + V("FirebaseDatabase#purgeOutstandingWrites", DatabasePurgeOutstandingWrites) \ + V("DatabaseReference#set", DatabaseReferenceSet) \ + V("DatabaseReference#setWithPriority", DatabaseReferenceSetWithPriority) \ + V("DatabaseReference#update", DatabaseReferenceUpdate) \ + V("DatabaseReference#setPriority", DatabaseReferenceSetPriority) \ + V("DatabaseReference#runTransaction", DatabaseReferenceRunTransaction) \ + V("OnDisconnect#set", OnDisconnectSet) \ + V("OnDisconnect#setWithPriority", OnDisconnectSetWithPriority) \ + V("OnDisconnect#update", OnDisconnectUpdate) \ + V("OnDisconnect#cancel", OnDisconnectCancel) \ + V("Query#get", QueryGet) \ + V("Query#keepSynced", QueryKeepSynced) \ + V("Query#observe", QueryObserve) + +#define V(Key, MethodName) \ + {Key, std::bind(&FirebaseDatabaseTizenPlugin::MethodName, this, \ + std::placeholders::_1, std::placeholders::_2)}, + + static std::unordered_map< + std::string, + std::function>)>> + method_map = {DATABASE_METHODS(V)}; +#undef V + + const auto& it = method_map.find(method_name); + if (it != method_map.end()) { + it->second(arguments, std::move(result)); + } else { + result->NotImplemented(); + } + } + + template + static void CommonOnCompletionCallback(const Future& future, void* data) { + static_assert( + std::is_same::value || std::is_same::value, + "Unhandled type"); + + TRACE(DATABASE, "[DONE]", future.status(), "error", future.error()); + PointerScope> result(data); + if (future.status() == FutureStatus::kFutureStatusComplete) { + if constexpr (std::is_same_v) { + future.error() == Error::kErrorNone + ? result->Success() + : result->Error(std::to_string(future.error()), + future.error_message()); + } else if constexpr (std::is_same_v) { + future.error() == Error::kErrorNone + ? result->Success( + EncodableValue(CreateDataSnapshotPayload(future.result()))) + : result->Error(std::to_string(future.error()), + future.error_message()); + } + } + } + + void DatabaseGoOnline(const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseFromArguments(arguments)->GoOnline(); + result->Success(); + } + + void DatabaseGoOffline(const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseFromArguments(arguments)->GoOffline(); + result->Success(); + } + + void DatabasePurgeOutstandingWrites( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseFromArguments(arguments)->PurgeOutstandingWrites(); + result->Success(); + } + + void DatabaseReferenceSet( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .SetValue(Conversion::ToFirebaseVariant(arguments, Constants::kValue)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void DatabaseReferenceSetWithPriority( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .SetValueAndPriority( + Conversion::ToFirebaseVariant(arguments, Constants::kValue), + Conversion::ToFirebaseVariant(arguments, Constants::kPriority)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void DatabaseReferenceUpdate( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .UpdateChildren( + Conversion::ToFirebaseVariant(arguments, Constants::kValue)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void DatabaseReferenceSetPriority( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .SetPriority( + Conversion::ToFirebaseVariant(arguments, Constants::kPriority)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void DatabaseReferenceRunTransaction( + const EncodableMap* arguments, + std::unique_ptr> result) { + // Local class TransactionMethodResult + // + class TransactionMethodResult : public MethodResult<> { + public: + void SuccessInternal(const EncodableValue* should_load) override { + TRACE(DATABASE, "[TRANSACTION/DART]", ToString(*should_load)); + + std::lock_guard lock(mutex_); + + if (auto* map = std::get_if(should_load)) { + wrapped_data_->aborted = + GetOptionalValue(map, Constants::kAborted) + .value_or(wrapped_data_->aborted); + + wrapped_data_->exception = + GetOptionalValue(map, Constants::kException) + .value_or(wrapped_data_->exception); + + CHECK_NOT_NULL(wrapped_data_->current_data); + wrapped_data_->current_data->set_value( + Conversion::ToFirebaseVariant(map, Constants::kValue)); + } else { + FATAL("The given result should be structed as EncodableMap"); + } + cv_.notify_one(); + } + + void ErrorInternal(const std::string& error_code, + const std::string& error_message, + const EncodableValue* error_details) override { + TRACE(DATABASE, error_code, error_message); + wrapped_data_->aborted = true; + cv_.notify_one(); + } + + void NotImplementedInternal() override { + TRACE(DATABASE); + wrapped_data_->aborted = true; + cv_.notify_one(); + } + + struct WrappedData { + WrappedData(MutableData* data) : current_data(data) { + CHECK_NOT_NULL(current_data); + } + MutableData* current_data; + bool aborted{false}; + bool exception{false}; + }; + + TransactionMethodResult(std::mutex& mutex, std::condition_variable& cv, + std::shared_ptr wrapped_data) + : mutex_(mutex), cv_(cv), wrapped_data_(wrapped_data) { + CHECK_NOT_NULL(wrapped_data_); + } + + private: + std::mutex& mutex_; + std::condition_variable& cv_; + std::shared_ptr wrapped_data_; + }; + + TRACE_SCOPE(DATABASE); + const auto transaction_key = + Conversion::ToFirebaseVariant(arguments, Constants::kTransactionKey) + .int64_value(); + const auto is_transaction_apply_locally = + Conversion::ToFirebaseVariant(arguments, + Constants::kTransactionApplyLocally) + .bool_value(); + + TRACE(DATABASE, "transactionKey", transaction_key); + TRACE(DATABASE, "transactionApplyLocally", is_transaction_apply_locally); + + GetDatabaseReferenceFromArguments(arguments) + .RunTransaction( + [this, transaction_key](MutableData* data) { + TRACE(DATABASE, "[TRANSACTION/FB]"); + + std::mutex mutex; + std::condition_variable cv; + + auto wrapped = + std::make_shared(data); + auto result = + std::make_unique(mutex, cv, wrapped); + + EncodableMap arguments = CreateMutableDataSnapshotPayload(data); + arguments.insert(EncodableValuePair(Constants::kTransactionKey, + transaction_key)); + + TRACE(DATABASE, ToString(arguments)); + + std::unique_lock lock(mutex); + channel_->InvokeMethod( + "FirebaseDatabase#callTransactionHandler", + std::make_unique(arguments), + std::move(result)); + cv.wait(lock); + + if (wrapped->aborted || wrapped->exception) { + return TransactionResult::kTransactionResultAbort; + } else { + return TransactionResult::kTransactionResultSuccess; + } + }, + is_transaction_apply_locally) + .OnCompletion( + [](const Future& future, void* data) { + TRACE_SCOPE0(DATABASE, "[DONE]", future.status(), "error", + future.error()); + PointerScope> result(data); + if (future.status() == FutureStatus::kFutureStatusComplete) { + const auto error = future.error(); + if (error == Error::kErrorNone) { + EncodableMap payload = + CreateDataSnapshotPayload(future.result()); + payload.insert( + EncodableValuePair(Constants::kCommitted, + Conversion::ToEncodableValue(true))); + result->Success(EncodableValue(payload)); + } else { + // TODO(daeyeon): verify if the following is a firebase issue. + // + // In the src/include/firebase/database/database_reference.h, + // it says that the code, 'kErrorTransactionAbortedByUser', + // means that a transaction was aborted because its + // transaction function returned 'kTransactionResultAbort', + // and the old value of the DataSnapshot will be returned. + // + // However, in src/desktop/core/repo.cc:672, the Complete() + // function is called with 'kErrorWriteCanceled' instead of + // 'kErrorTransactionAbortedByUser'. This is our question + // mark. Here we need to check for 'kErrorWriteCanceled' also. + // + // Seeing the code passed in other platform implementations, + // we can see that 'kErrorTransactionAbortedByUser' is passed + // as mentioned in the documentation. + // - src/android/util_android.cc:182 + // - src/ios/database_reference_ios.mm:165 + + if (error == Error::kErrorTransactionAbortedByUser || + error == Error::kErrorWriteCanceled) { + EncodableMap payload = + CreateDataSnapshotPayload(future.result()); + payload.insert(EncodableValuePair( + Constants::kCommitted, + Conversion::ToEncodableValue(false))); + result->Success(EncodableValue(payload)); + } else { + result->Error(std::to_string(error), + future.error_message()); + } + } + } + }, + result.release()); + } + + void OnDisconnectSet(const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .OnDisconnect() + ->SetValue(Conversion::ToFirebaseVariant( + GetEncodableValue(arguments, Constants::kValue))) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void OnDisconnectSetWithPriority( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .OnDisconnect() + ->SetValueAndPriority( + Conversion::ToFirebaseVariant(arguments, Constants::kValue), + Conversion::ToFirebaseVariant(arguments, Constants::kPriority)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void OnDisconnectUpdate( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .OnDisconnect() + ->UpdateChildren( + Conversion::ToFirebaseVariant(arguments, Constants::kValue)) + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void OnDisconnectCancel( + const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseReferenceFromArguments(arguments) + .OnDisconnect() + ->Cancel() + .OnCompletion(CommonOnCompletionCallback, result.release()); + } + + void QueryGet(const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + GetDatabaseQueryFromArguments(arguments).GetValue().OnCompletion( + CommonOnCompletionCallback, result.release()); + } + + void QueryKeepSynced(const EncodableMap* arguments, + std::unique_ptr> result) { + TRACE_SCOPE(DATABASE); + auto keep_sync = + GetOptionalValue(arguments, Constants::kValue).value(); + GetDatabaseQueryFromArguments(arguments).SetKeepSynchronized(keep_sync); + result->Success(); + } + + void QueryObserve(const EncodableMap* arguments, + std::unique_ptr> result) { + // Local class FirebaseChildListener + // + class FirebaseChildListener : public ChildListener { + public: + using ObserveHandler = std::function; + + using CancelHandler = + std::function; + + FirebaseChildListener( + std::string event_channel_name, + std::shared_ptr> channel) { + event_channel_name_ = event_channel_name; + channel_ = channel; + } + + ~FirebaseChildListener() { SetHandler(nullptr, nullptr); } + + void OnChildAdded(const DataSnapshot& snapshot, + const char* previous_sibling_key) override { + TRACE_SCOPE(FB_LISTEN); + NotifyObserveEvent(Constants::kChildAdded, snapshot, + previous_sibling_key); + } + + void OnChildChanged(const DataSnapshot& snapshot, + const char* previous_sibling_key) override { + TRACE_SCOPE(FB_LISTEN); + NotifyObserveEvent(Constants::kChildChanged, snapshot, + previous_sibling_key); + } + + void OnChildMoved(const DataSnapshot& snapshot, + const char* previous_sibling_key) override { + TRACE_SCOPE(FB_LISTEN); + NotifyObserveEvent(Constants::kChildMoved, snapshot, + previous_sibling_key); + } + + void OnChildRemoved(const DataSnapshot& snapshot) override { + TRACE_SCOPE(FB_LISTEN); + NotifyObserveEvent(Constants::kChildRemove, snapshot, nullptr); + } + + void OnCancelled(const Error& error, const char* error_message) override { + TRACE_SCOPE(FB_LISTEN); + NotifyCancelEvent(error, error_message); + } + + void NotifyObserveEvent(const std::string& event_type, + const DataSnapshot& snapshot, + const char* previous_sibling_key) { + TRACE_SCOPE(FB_LISTEN); + if (observe_handler_) { + std::lock_guard lock(mutex_); + observe_handler_(event_type, snapshot, previous_sibling_key); + } + } + + void NotifyCancelEvent(const Error& error, const char* error_message) { + TRACE_SCOPE(FB_LISTEN); + if (cancel_handler_) { + std::lock_guard lock(mutex_); + cancel_handler_(error, error_message); + } + } + + void SetHandler(ObserveHandler observe_handler, + CancelHandler cancel_handler) { + TRACE_SCOPE(FB_LISTEN); + std::lock_guard lock(mutex_); + observe_handler_ = observe_handler; + cancel_handler_ = cancel_handler; + } + + private: + std::mutex mutex_; + std::string event_channel_name_; + std::shared_ptr> channel_; + ObserveHandler observe_handler_{nullptr}; + CancelHandler cancel_handler_{nullptr}; + }; + + // Local class FlutterStreamHandler + // + // The class methods are called whenever the stream is listened to or + // canceled in the Flutter. + class FlutterStreamHandler : public StreamHandler { + public: + FlutterStreamHandler( + std::shared_ptr query, + std::shared_ptr> channel, + FirebaseChildListener* childListener) + : query_(query), channel_(channel), childListener_(childListener) {} + + ~FlutterStreamHandler() { + if (childListener_) { + TRACE(FT_STREAM, + "[!] delete childListener_, but it's supposed to be deleted " + "inside OnCancelInternal()"); + query_->RemoveChildListener(childListener_); + delete childListener_; + } + } + + protected: + std::unique_ptr> OnListenInternal( + const EncodableValue* arguments, + std::unique_ptr>&& events) override { + TRACE_SCOPE(FT_STREAM, ToString(*arguments)); + + // NOTE(daeyeon): Any event_channel_name bound to this handler is + // unique, and it is expected that this function will be called only + // once for the event_channel_name. Therefore, we assume that the sink + // should always be nullptr at this point. + CHECK_NULL(events_); + CHECK_NOT_NULL(childListener_); + + events_ = std::move(events); + + // The arguments should be an EncodableMap including "eventType": + // https://github.com/firebase/flutterfire/blob/9be203f10dd20c6bc9bd0588ea8062e326de767c/packages/firebase_database/firebase_database_platform_interface/lib/src/method_channel/method_channel_query.dart#L58 + CHECK(std::holds_alternative(*arguments)); + + auto map = std::get(*arguments); + event_type_ = + GetOptionalValue(&map, Constants::kEventType).value(); + + // Register a handler to send an event + childListener_->SetHandler( + [this](const std::string& event_type, const DataSnapshot& snapshot, + const char* previous_sibling_key) { + TRACE_SCOPE(FT_STREAM, "listen:", event_type_, + "receive:", event_type, "key:", + previous_sibling_key == nullptr + ? "{}" + : previous_sibling_key); + + if (event_type_ == event_type) { + EncodableMap payload = CreateDataSnapshotPayload(&snapshot); + payload.insert( + EncodableValuePair(Constants::kEventType, event_type)); + if (previous_sibling_key) { + payload.insert(EncodableValuePair( + Constants::kPreviousChildKey, previous_sibling_key)); + } + events_->Success(EncodableValue(payload)); + } + }, + [this](const Error& error, const char* error_message) { + TRACE_SCOPE(FT_STREAM, "error_message", error_message); + events_->Error(std::to_string(error), error_message); + }); + return nullptr; + } + + std::unique_ptr> OnCancelInternal( + const flutter::EncodableValue* arguments) override { + TRACE_SCOPE(FT_STREAM, ToString(*arguments)); + + // Here, release all the associated resources. + + // Release events + events_.reset(); + + // Release the listener referring to query_. + query_->RemoveChildListener(childListener_); + delete childListener_; + childListener_ = nullptr; + + // Calling this will release this instance itself. + channel_->SetStreamHandler(nullptr); + + return nullptr; + } + + private: + std::shared_ptr query_; + std::shared_ptr> channel_; + std::string event_type_; + std::unique_ptr> events_; + FirebaseChildListener* childListener_; + }; + + TRACE_SCOPE(DATABASE); + const auto prefix = GetOptionalValue( + arguments, Constants::kEventChannelNamePrefix) + .value(); + const std::string event_channel_name = + prefix + "#" + std::to_string(++listener_count_); + + TRACE(DATABASE, "event_channel_name:", event_channel_name); + + Query query = GetDatabaseQueryFromArguments(arguments); + + // Create an event channel + auto channel = std::make_shared>( + binary_messenger_, event_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + + // Create a listener to this query. This is supposed to be deleted in + // FirebaseChildListener::OnCancelInternal. + auto listener = new FirebaseChildListener(event_channel_name, channel); + query.AddChildListener(listener); + + // Create a stream handler + auto stream_handler = std::make_unique( + std::make_shared(query), channel, listener); + + // Register a stream handler on this channel + channel->SetStreamHandler(std::move(stream_handler)); + + result->Success(EncodableValue(event_channel_name)); + } + + private: + std::unique_ptr> channel_; + int listener_count_{0}; + BinaryMessenger* binary_messenger_{nullptr}; +}; + +} // namespace + +void FirebaseDatabaseTizenPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + FirebaseDatabaseTizenPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/firebase_database/tizen/src/firebase_database_utils.cc b/packages/firebase_database/tizen/src/firebase_database_utils.cc new file mode 100644 index 0000000..21ea6de --- /dev/null +++ b/packages/firebase_database/tizen/src/firebase_database_utils.cc @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "firebase_database_utils.h" + +#include +#include +#include +#include + +#include "common/conversion.h" +#include "common/to_string.h" +#include "common/trace.h" +#include "common/utils.h" +#include "constants.h" + +using firebase::App; +using firebase::InitResult; +using firebase::LogLevel; +using firebase::database::Database; +using firebase::database::DatabaseReference; +using firebase::database::DataSnapshot; +using firebase::database::MutableData; +using firebase::database::Query; +using flutter::EncodableList; +using flutter::EncodableMap; +using flutter::EncodableValue; + +using EncodableValuePair = std::pair; + +static std::unordered_map cached_database_map; + +Database* GetDatabaseFromArguments(const EncodableMap* args) { + CHECK_NOT_NULL(args); + const std::string app_name = + GetOptionalValue(args, Constants::kAppName) + .value_or(Constants::kDefalutAppName); + const std::string database_url = + GetOptionalValue(args, Constants::kDatabaseURL).value_or(""); + const std::string instance_key = app_name + database_url; + + const auto& it = cached_database_map.find(instance_key); + if (it != cached_database_map.end()) { + return it->second; + } + + App* app = App::GetInstance(app_name.c_str()); + CHECK_NOT_NULL(app); + + Database* database = nullptr; + InitResult result; + + if (database_url.length() == 0) { + database = Database::GetInstance(app, &result); + } else { + database = Database::GetInstance(app, database_url.c_str(), &result); + } + CHECK_NOT_NULL(database); + + if (result != InitResult::kInitResultSuccess) { + TRACE(DATABASE, "[FAIL] Database::GetInstance with", result); + } + + database->set_persistence_enabled( + GetOptionalValue(args, Constants::kDatabasePersistenceEnabled) + .value_or(false)); + + if (GetOptionalValue(args, Constants::kDatabaseCacheSizeBytes)) { + TRACE(DATABASE, "[!] cacheSizeBytes isn't supported."); + } + + auto log_level = LogLevel::kLogLevelError; + if (GetOptionalValue(args, Constants::kDatabaseLoggingEnabled) + .value_or(false)) { + log_level = LogLevel::kLogLevelWarning; + } + database->set_log_level(log_level); + + if (GetOptionalValue(args, Constants::kDatabaseEmulatorHost) || + GetOptionalValue(args, Constants::kDatabaseEmulatorPort)) { + TRACE(DATABASE, "[!] Emulator isn't supported."); + } + + cached_database_map.emplace(instance_key, database); + + return database; +} + +DatabaseReference GetDatabaseReferenceFromArguments( + const EncodableMap* arguments) { + CHECK_NOT_NULL(arguments); + Database* database = GetDatabaseFromArguments(arguments); + + auto path_maybe = GetOptionalValue(arguments, Constants::kPath); + CHECK(path_maybe); + auto path = path_maybe.value(); + TRACE(DATABASE, "path:", path); + return database->GetReference(path.c_str()); +} + +static Query ApplyOrderModifier(Query& query, const EncodableMap& modifier) { + const auto name = + GetOptionalValue(&modifier, Constants::kName).value(); + + if (name == Constants::kOrderByChild) { + return query.OrderByChild( + GetOptionalValue(&modifier, Constants::kPath).value()); + } else if (name == Constants::kOrderByKey) { + return query.OrderByKey(); + } else if (name == Constants::kOrderByValue) { + return query.OrderByValue(); + } else if (name == Constants::kOrderByPriority) { + return query.OrderByPriority(); + } else { + TRACE(DATABASE, "[!] Unknown modifier"); + } + return query; +} + +static Query ApplyCursorModifier(Query& query, const EncodableMap& modifier) { + const auto name = + GetOptionalValue(&modifier, Constants::kName).value(); + + if (name == Constants::kStartAt) { + return query.StartAt( + Conversion::ToFirebaseVariant(&modifier, Constants::kValue)); + } else if (name == Constants::kEndAt) { + return query.EndAt( + Conversion::ToFirebaseVariant(&modifier, Constants::kValue)); + } else if (name == Constants::kStartAfter) { + TRACE(DATABASE, "[!] 'startAfter' isn't supported"); + } else if (name == Constants::kEndBefore) { + TRACE(DATABASE, "[!] 'endBefore' isn't supported"); + } else { + TRACE(DATABASE, "[!] Unknown modifier or unimplemented"); + } + return query; +} + +static Query ApplyLimitModifier(Query& query, const EncodableMap& modifier) { + const auto name = + GetOptionalValue(&modifier, Constants::kName).value(); + const auto limit = static_cast( + GetEncodableValue(&modifier, Constants::kLimit).LongValue()); + + if (name == Constants::kLimitToFirst) { + return query.LimitToFirst(limit); + } else if (name == Constants::kLimitToLast) { + return query.LimitToLast(limit); + } else { + TRACE(DATABASE, "[!] Unknown modifier or unimplemented"); + } + return query; +} + +Query GetDatabaseQueryFromArguments(const EncodableMap* arguments) { + TRACE_SCOPE0(DATABASE); + Query query = GetDatabaseReferenceFromArguments(arguments); + + auto modifiers_maybe = + GetOptionalValue(arguments, Constants::kModifiers); + + CHECK(modifiers_maybe); + + auto modifiers = modifiers_maybe.value(); + TRACE(DATABASE, "modifiers size:", modifiers.size()); + + for (const EncodableValue& value : modifiers) { + auto modifier = std::get(value); + auto type = + GetOptionalValue(&modifier, Constants::kType).value(); + if (type == Constants::kOrderBy) { + query = ApplyOrderModifier(query, modifier); + } else if (type == Constants::kCursor) { + query = ApplyCursorModifier(query, modifier); + } else if (type == Constants::kLimit) { + query = ApplyLimitModifier(query, modifier); + } + } + + return query; +} + +EncodableMap CreateDataSnapshotPayload(const DataSnapshot* snapshot) { + CHECK_NOT_NULL(snapshot); + + TRACE_SCOPE(DATABASE, ToString(*snapshot)); + + EncodableMap map; + map.insert(EncodableValuePair(Constants::kKey, snapshot->key_string())); + map.insert(EncodableValuePair( + Constants::kValue, Conversion::ToEncodableValue(snapshot->value()))); + map.insert( + EncodableValuePair(Constants::kPriority, + Conversion::ToEncodableValue(snapshot->priority()))); + + if (snapshot->has_children()) { + std::vector childKeys; + for (const auto& child : snapshot->children()) { + childKeys.push_back(child.key_string()); + } + map.insert(EncodableValuePair(Constants::kChildKeys, + Conversion::ToEncodableValue(childKeys))); + } + + return EncodableMap{ + {EncodableValue(Constants::kSnapshot), EncodableValue(map)}}; +} + +EncodableMap CreateMutableDataSnapshotPayload(MutableData* snapshot) { + CHECK_NOT_NULL(snapshot); + + TRACE_SCOPE(DATABASE, ToString(*snapshot)); + + EncodableMap map; + map.insert(EncodableValuePair(Constants::kKey, snapshot->key_string())); + map.insert(EncodableValuePair( + Constants::kValue, Conversion::ToEncodableValue(snapshot->value()))); + map.insert( + EncodableValuePair(Constants::kPriority, + Conversion::ToEncodableValue(snapshot->priority()))); + + if (snapshot->children_count() > 0) { + std::vector childKeys; + for (const auto& child : snapshot->children()) { + childKeys.push_back(child.key_string()); + } + map.insert(EncodableValuePair(Constants::kChildKeys, + Conversion::ToEncodableValue(childKeys))); + } + + return EncodableMap{ + {EncodableValue(Constants::kSnapshot), EncodableValue(map)}}; +} diff --git a/packages/firebase_database/tizen/src/firebase_database_utils.h b/packages/firebase_database/tizen/src/firebase_database_utils.h new file mode 100644 index 0000000..3245921 --- /dev/null +++ b/packages/firebase_database/tizen/src/firebase_database_utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +// Database + +firebase::database::Database* GetDatabaseFromArguments( + const flutter::EncodableMap* arguments); + +firebase::database::DatabaseReference GetDatabaseReferenceFromArguments( + const flutter::EncodableMap* arguments); + +firebase::database::Query GetDatabaseQueryFromArguments( + const flutter::EncodableMap* arguments); + +// Message Channel Payload + +flutter::EncodableMap CreateDataSnapshotPayload( + const firebase::database::DataSnapshot* snapshot); + +flutter::EncodableMap CreateMutableDataSnapshotPayload( + firebase::database::MutableData* snapshot); diff --git a/packages/firebase_database/tizen/tar_url.sh b/packages/firebase_database/tizen/tar_url.sh new file mode 100755 index 0000000..8ab36df --- /dev/null +++ b/packages/firebase_database/tizen/tar_url.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e + +USAGE=$(cat << EOF +Usage: $(basename "$0") [DEST_DIR] [STRIP_COMPONENTS_NUMBER] + +Description: +A script to download a tar file from a given URL and extract it. It skips the extraction +processs if the contents of the file have already been extracted for the same URL. + +Arguments: + FILE_URL The URL address of the tar file to download. + DEST_DIR The directory path to the tar file for extraction. (default: \$HOME/.firebaseSDK) + STRIP_COMPONENTS_NUMBER + The number of leading components to strip during the tar extraction. (default: 1) + +Example: + $(basename "$0") https://example.com/file.tar.gz /path/to 0 +EOF +) + +if [ -z "$1" ]; then + echo "$USAGE" + exit 1 +fi + +FILE_URL=$1 +DEST_DIR=${2:-${FLUTTER_BUILD_DIR}/.firebaseSDK} +TAR_SNUM=${3:-1} + +RECORD_FILE="${DEST_DIR}/VERSION" + +if [ -e "$RECORD_FILE" ] && [ "$FILE_URL" = $(head -n 1 "$RECORD_FILE") ]; then + echo "$RECORD_FILE exists with the same URL." +else + [ ! -d "$DEST_DIR" ] && mkdir -v "$DEST_DIR" + curl -L $FILE_URL | tar -xz --strip-components=$TAR_SNUM -C "$DEST_DIR" + echo $FILE_URL > "$RECORD_FILE" +fi From 3e6be1ef7be57df58990c0fec4989801752e4fdf Mon Sep 17 00:00:00 2001 From: Daeyeon Jeong Date: Mon, 31 Jul 2023 18:42:09 +0900 Subject: [PATCH 2/5] fixup! feat(database): add initial firebase database Signed-off-by: Daeyeon Jeong --- packages/firebase_database/README.md | 17 ++++++++++++++++- .../firebase_database/analysis_options.yaml | 4 +--- packages/firebase_database/pubspec.yaml | 5 ++--- .../tizen/src/firebase_database_tizen_plugin.cc | 3 +-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/firebase_database/README.md b/packages/firebase_database/README.md index 18f0f0c..fe4a284 100644 --- a/packages/firebase_database/README.md +++ b/packages/firebase_database/README.md @@ -6,7 +6,7 @@ It offers experimental features for using Firebase on Flutter for Tizen. It work # Usage -To use this package, you need to include `firebase_database_tizen` as a dependency alongside `firebase_database` and `firebase_database_platform_interface` in your `pubspec.yaml`. Please note that `firebase_database_tizen` implementation is not officially endorsed for `firebase_database`. +To use this package, you need to include `firebase_database_tizen` as a dependency alongside `firebase_database` in your `pubspec.yaml`. Please note that `firebase_database_tizen` implementation is not officially endorsed for `firebase_database`. ```yaml dependencies: @@ -20,6 +20,21 @@ Then you can import `firebase_database` in your Dart code: import 'package:firebase_database/firebase_database.dart'; ``` +## Required privileges + +To use this plugin in a Tizen application, you may need to declare the following privileges in your `tizen-manifest.xml` file. + +```xml + + http://tizen.org/privilege/internet + +``` + +- `http://tizen.org/privilege/internet` allows the application to access the Internet. + +For the details on Tizen privileges, please see [Tizen Docs: API Privileges](https://docs.tizen.org/application/dotnet/get-started/api-privileges). + + # Limitations The following features are currently unavailable as they're not supported by the version of Firebase C++ SDK for Linux that this plugin is currently based on. diff --git a/packages/firebase_database/analysis_options.yaml b/packages/firebase_database/analysis_options.yaml index a854094..216f401 100644 --- a/packages/firebase_database/analysis_options.yaml +++ b/packages/firebase_database/analysis_options.yaml @@ -2,6 +2,4 @@ include: ../../analysis_options.yaml linter: rules: - public_member_api_docs: false - depend_on_referenced_packages: false - library_private_types_in_public_api: false + public_member_api_docs: true diff --git a/packages/firebase_database/pubspec.yaml b/packages/firebase_database/pubspec.yaml index 9770425..6000f95 100644 --- a/packages/firebase_database/pubspec.yaml +++ b/packages/firebase_database/pubspec.yaml @@ -1,6 +1,5 @@ name: firebase_database_tizen -description: Flutter plugin for Firebase Database, a cloud-hosted NoSQL database - with realtime data syncing across Android and iOS clients, and offline access. +description: The Firebase Database implementation for Tizen. version: 0.1.0 publish_to: 'none' @@ -20,9 +19,9 @@ dependency_overrides: path: ../firebase_core dev_dependencies: + flutter_lints: ^2.0.0 flutter_test: sdk: flutter - flutter_lints: ^2.0.0 flutter: plugin: diff --git a/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc index 9660539..851cfd4 100644 --- a/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc +++ b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc @@ -126,6 +126,7 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { std::unique_ptr>)>> method_map = {DATABASE_METHODS(V)}; #undef V +#undef DATABASE_METHODS const auto& it = method_map.find(method_name); if (it != method_map.end()) { @@ -606,8 +607,6 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { const flutter::EncodableValue* arguments) override { TRACE_SCOPE(FT_STREAM, ToString(*arguments)); - // Here, release all the associated resources. - // Release events events_.reset(); From 845e8f0125ebdfa7c730a05aeb22b73fccd5ff6a Mon Sep 17 00:00:00 2001 From: Daeyeon Jeong Date: Wed, 2 Aug 2023 13:02:30 +0900 Subject: [PATCH 3/5] fixup! fixup! feat(database): add initial firebase database Signed-off-by: Daeyeon Jeong --- .../src/firebase_database_tizen_plugin.cc | 190 +++++++++++++----- 1 file changed, 135 insertions(+), 55 deletions(-) diff --git a/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc index 851cfd4..eb3fc85 100644 --- a/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc +++ b/packages/firebase_database/tizen/src/firebase_database_tizen_plugin.cc @@ -51,6 +51,7 @@ using firebase::database::Error; using firebase::database::MutableData; using firebase::database::Query; using firebase::database::TransactionResult; +using firebase::database::ValueListener; using flutter::BinaryMessenger; using flutter::EncodableMap; using flutter::EncodableValue; @@ -445,6 +446,51 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { void QueryObserve(const EncodableMap* arguments, std::unique_ptr> result) { + // Local class FirebaseValueListener + // + class FirebaseValueListener : public ValueListener { + public: + using ObserveHandler = std::function; + + using CancelHandler = + std::function; + + FirebaseValueListener(std::string event_channel_name) { + event_channel_name_ = event_channel_name; + } + ~FirebaseValueListener() { SetHandler(nullptr, nullptr); } + + void OnValueChanged(const DataSnapshot& snapshot) override { + TRACE_SCOPE(FB_LISTEN); + if (observe_handler_) { + std::lock_guard lock(mutex_); + observe_handler_(snapshot); + } + }; + + void OnCancelled(const Error& error, const char* error_message) override { + TRACE_SCOPE(FB_LISTEN); + if (cancel_handler_) { + std::lock_guard lock(mutex_); + cancel_handler_(error, error_message); + } + }; + + void SetHandler(ObserveHandler observe_handler, + CancelHandler cancel_handler) { + TRACE_SCOPE(FB_LISTEN); + std::lock_guard lock(mutex_); + observe_handler_ = observe_handler; + cancel_handler_ = cancel_handler; + } + + private: + std::mutex mutex_; + std::string event_channel_name_; + ObserveHandler observe_handler_{nullptr}; + CancelHandler cancel_handler_{nullptr}; + }; + // Local class FirebaseChildListener // class FirebaseChildListener : public ChildListener { @@ -456,15 +502,14 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { using CancelHandler = std::function; - FirebaseChildListener( - std::string event_channel_name, - std::shared_ptr> channel) { + FirebaseChildListener(std::string event_channel_name) { event_channel_name_ = event_channel_name; - channel_ = channel; } ~FirebaseChildListener() { SetHandler(nullptr, nullptr); } + const std::string& event_channel_name() { return event_channel_name_; } + void OnChildAdded(const DataSnapshot& snapshot, const char* previous_sibling_key) override { TRACE_SCOPE(FB_LISTEN); @@ -488,7 +533,7 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { void OnChildRemoved(const DataSnapshot& snapshot) override { TRACE_SCOPE(FB_LISTEN); - NotifyObserveEvent(Constants::kChildRemove, snapshot, nullptr); + NotifyObserveEvent(Constants::kChildRemove, snapshot, ""); } void OnCancelled(const Error& error, const char* error_message) override { @@ -525,7 +570,6 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { private: std::mutex mutex_; std::string event_channel_name_; - std::shared_ptr> channel_; ObserveHandler observe_handler_{nullptr}; CancelHandler cancel_handler_{nullptr}; }; @@ -539,18 +583,12 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { FlutterStreamHandler( std::shared_ptr query, std::shared_ptr> channel, - FirebaseChildListener* childListener) - : query_(query), channel_(channel), childListener_(childListener) {} + std::string event_channel_name) + : query_(query), + channel_(channel), + event_channel_name_(event_channel_name) {} - ~FlutterStreamHandler() { - if (childListener_) { - TRACE(FT_STREAM, - "[!] delete childListener_, but it's supposed to be deleted " - "inside OnCancelInternal()"); - query_->RemoveChildListener(childListener_); - delete childListener_; - } - } + ~FlutterStreamHandler() { ReleaseListener(); } protected: std::unique_ptr> OnListenInternal( @@ -560,10 +598,11 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { // NOTE(daeyeon): Any event_channel_name bound to this handler is // unique, and it is expected that this function will be called only - // once for the event_channel_name. Therefore, we assume that the sink - // should always be nullptr at this point. + // once for the event_channel_name. Therefore, we assume that the + // sink, events_, should always be nullptr at this point. CHECK_NULL(events_); - CHECK_NOT_NULL(childListener_); + CHECK_NULL(childListener_); + CHECK_NULL(valueListener_); events_ = std::move(events); @@ -575,31 +614,66 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { event_type_ = GetOptionalValue(&map, Constants::kEventType).value(); - // Register a handler to send an event - childListener_->SetHandler( - [this](const std::string& event_type, const DataSnapshot& snapshot, - const char* previous_sibling_key) { - TRACE_SCOPE(FT_STREAM, "listen:", event_type_, - "receive:", event_type, "key:", - previous_sibling_key == nullptr - ? "{}" - : previous_sibling_key); - - if (event_type_ == event_type) { - EncodableMap payload = CreateDataSnapshotPayload(&snapshot); - payload.insert( - EncodableValuePair(Constants::kEventType, event_type)); - if (previous_sibling_key) { + TRACE(FT_STREAM, "type:", event_type_, "channel:", event_channel_name_); + + // Create and register a handler to send events + if (event_type_ == Constants::kValue) { + valueListener_ = + std::make_shared(event_channel_name_); + + valueListener_->SetHandler( + [this](const DataSnapshot& snapshot) { + TRACE_SCOPE(FT_STREAM); + events_->Success( + EncodableValue(CreateDataSnapshotPayload(&snapshot))); + }, + [this](const Error& error, const char* error_message) { + TRACE_SCOPE(FT_STREAM, "error_message", error_message); + events_->Error(std::to_string(error), error_message); + }); + + query_->AddValueListener(valueListener_.get()); + } else { + childListener_ = + std::make_shared(event_channel_name_); + + childListener_->SetHandler( + [this](const std::string& event_type, + const DataSnapshot& snapshot, + const char* previous_sibling_key) { + CHECK_NOT_NULL(previous_sibling_key); + bool has_previous_sibling_key = previous_sibling_key[0] != '\0'; + + TRACE_SCOPE( + FT_STREAM, "type:", event_type_, "previous_sibling_key:", + has_previous_sibling_key ? previous_sibling_key : "{}"); + + if (event_type_ == event_type) { + EncodableMap payload = CreateDataSnapshotPayload(&snapshot); + payload.insert( + EncodableValuePair(Constants::kEventType, event_type)); + + // Note: if the previous_sibling_key is an empty string, it + // should be represented as a null EncodableValue. This + // situation commonly occurs when the cloud backend does not + // have any entity. payload.insert(EncodableValuePair( - Constants::kPreviousChildKey, previous_sibling_key)); + Constants::kPreviousChildKey, + has_previous_sibling_key + ? EncodableValue(previous_sibling_key) + : EncodableValue())); + + events_->Success(EncodableValue(payload)); } - events_->Success(EncodableValue(payload)); - } - }, - [this](const Error& error, const char* error_message) { - TRACE_SCOPE(FT_STREAM, "error_message", error_message); - events_->Error(std::to_string(error), error_message); - }); + }, + [this](const Error& error, const char* error_message) { + TRACE_SCOPE(FT_STREAM, "error_message", error_message); + events_->Error(std::to_string(error), error_message); + }); + + query_->AddChildListener(childListener_.get()); + } + return nullptr; } @@ -607,13 +681,9 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { const flutter::EncodableValue* arguments) override { TRACE_SCOPE(FT_STREAM, ToString(*arguments)); - // Release events events_.reset(); - // Release the listener referring to query_. - query_->RemoveChildListener(childListener_); - delete childListener_; - childListener_ = nullptr; + ReleaseListener(); // Calling this will release this instance itself. channel_->SetStreamHandler(nullptr); @@ -622,11 +692,25 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { } private: + void ReleaseListener() { + if (childListener_) { + query_->RemoveChildListener(childListener_.get()); + childListener_.reset(); + } + + if (valueListener_) { + query_->RemoveValueListener(valueListener_.get()); + valueListener_.reset(); + } + } + std::shared_ptr query_; std::shared_ptr> channel_; std::string event_type_; std::unique_ptr> events_; - FirebaseChildListener* childListener_; + std::string event_channel_name_; + std::shared_ptr valueListener_; + std::shared_ptr childListener_; }; TRACE_SCOPE(DATABASE); @@ -645,18 +729,14 @@ class FirebaseDatabaseTizenPlugin : public flutter::Plugin { binary_messenger_, event_channel_name, &flutter::StandardMethodCodec::GetInstance()); - // Create a listener to this query. This is supposed to be deleted in - // FirebaseChildListener::OnCancelInternal. - auto listener = new FirebaseChildListener(event_channel_name, channel); - query.AddChildListener(listener); - // Create a stream handler auto stream_handler = std::make_unique( - std::make_shared(query), channel, listener); + std::make_shared(query), channel, event_channel_name); // Register a stream handler on this channel channel->SetStreamHandler(std::move(stream_handler)); + // Set the result with the registered event channel name result->Success(EncodableValue(event_channel_name)); } From 0651ebfa31d8c565eeb04a2dbb2c401c63b394d0 Mon Sep 17 00:00:00 2001 From: Daeyeon Jeong Date: Wed, 2 Aug 2023 13:57:42 +0900 Subject: [PATCH 4/5] fixup! fixup! fixup! feat(database): add initial firebase database Signed-off-by: Daeyeon Jeong --- .../firebase_database/example/lib/main.dart | 296 ++++++++++++------ .../firebase_database/example/pubspec.yaml | 5 +- 2 files changed, 200 insertions(+), 101 deletions(-) diff --git a/packages/firebase_database/example/lib/main.dart b/packages/firebase_database/example/lib/main.dart index ebadd96..23846fa 100644 --- a/packages/firebase_database/example/lib/main.dart +++ b/packages/firebase_database/example/lib/main.dart @@ -1,140 +1,240 @@ -// Copyright 2022, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; -import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/foundation.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:firebase_database/ui/firebase_animated_list.dart'; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; import 'package:flutter/material.dart'; + import 'firebase_options.dart'; +// Change to false to use live database instance. +const USE_DATABASE_EMULATOR = true; +// The port we've set the Firebase Database emulator to run on via the +// `firebase.json` configuration file. +const emulatorPort = 9000; +// Android device emulators consider localhost of the host machine as 10.0.2.2 +// so let's use that if running on Android. +final emulatorHost = + (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) + ? '10.0.2.2' + : 'localhost'; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); - runApp(const MyApp()); + + if (USE_DATABASE_EMULATOR) { + FirebaseDatabase.instance.useDatabaseEmulator(emulatorHost, emulatorPort); + } + + runApp( + const MaterialApp( + title: 'Flutter Database Example', + home: MyHomePage(), + ), + ); } -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key}) : super(key: key); - // This widget is the root of your application. @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } + _MyHomePageState createState() => _MyHomePageState(); } -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); +class _MyHomePageState extends State { + int _counter = 0; + late DatabaseReference _counterRef; + late DatabaseReference _messagesRef; + late StreamSubscription _counterSubscription; + late StreamSubscription _messagesSubscription; + bool _anchorToBottom = false; + + String _kTestKey = 'Hello'; + String _kTestValue = 'world!'; + FirebaseException? _error; + bool initialized = false; + + @override + void initState() { + init(); + super.initState(); + } - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. + Future init() async { + _counterRef = FirebaseDatabase.instance.ref('counter'); - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". + final database = FirebaseDatabase.instance; - final String title; + _messagesRef = database.ref('messages'); + + database.setLoggingEnabled(false); + + if (!kIsWeb) { + database.setPersistenceEnabled(true); + database.setPersistenceCacheSizeBytes(10000000); + } + + if (!kIsWeb) { + await _counterRef.keepSynced(true); + } + + setState(() { + initialized = true; + }); + + try { + final counterSnapshot = await _counterRef.get(); + + print( + 'Connected to directly configured database and read ' + '${counterSnapshot.value}', + ); + } catch (err) { + print(err); + } + + _counterSubscription = _counterRef.onValue.listen( + (DatabaseEvent event) { + setState(() { + _error = null; + _counter = (event.snapshot.value ?? 0) as int; + }); + }, + onError: (Object o) { + final error = o as FirebaseException; + setState(() { + _error = error; + }); + }, + ); + + final messagesQuery = _messagesRef.limitToLast(10); + + _messagesSubscription = messagesQuery.onChildAdded.listen( + (DatabaseEvent event) { + print('Child added: ${event.snapshot.value}'); + }, + onError: (Object o) { + final error = o as FirebaseException; + print('Error: ${error.code} ${error.message}'); + }, + ); + } @override - State createState() => _MyHomePageState(); -} + void dispose() { + super.dispose(); + _messagesSubscription.cancel(); + _counterSubscription.cancel(); + } -class _MyHomePageState extends State { - int _counter = 0; + Future _increment() async { + await _counterRef.set(ServerValue.increment(1)); + + await _messagesRef + .push() + .set({_kTestKey: '$_kTestValue $_counter'}); + } + + Future _incrementAsTransaction() async { + try { + final transactionResult = await _counterRef.runTransaction((mutableData) { + return Transaction.success((mutableData as int? ?? 0) + 1); + }); + + if (transactionResult.committed) { + final newMessageRef = _messagesRef.push(); + await newMessageRef.set({ + _kTestKey: '$_kTestValue ${transactionResult.snapshot.value}' + }); + } + } on FirebaseException catch (e) { + print(e.message); + } + } + + Future _deleteMessage(DataSnapshot snapshot) async { + final messageRef = _messagesRef.child(snapshot.key!); + await messageRef.remove(); + } + + void _setAnchorToBottom(bool? value) { + if (value == null) { + return; + } - void _incrementCounter() { setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; + _anchorToBottom = value; }); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. + if (!initialized) return Container(); + return Scaffold( appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), + title: const Text('Flutter Database Example'), ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () async { - // Running these APIs manually as they're failing on CI due to required keychain sharing entitlements - // See this issue https://github.com/firebase/flutterfire/issues/9538 - // You will also need to add the keychain sharing entitlements to this test app and sign code with development team for app & tests to successfully run - if (Platform.isMacOS && kDebugMode) { - // ignore_for_file: avoid_print - // Wait a little so we don't get a delete-pending exception - await Future.delayed(const Duration(seconds: 8)); - } - }, - child: const Text('Test macOS tests manually'), + body: Column( + children: [ + Flexible( + child: Center( + child: _error == null + ? Text( + 'Button tapped $_counter time${_counter == 1 ? '' : 's'}.\n\n' + 'This includes all devices, ever.', + ) + : Text( + 'Error retrieving button tap count:\n${_error!.message}', + ), ), - const Text( - 'You have pushed the button this many times:', + ), + ElevatedButton( + onPressed: _incrementAsTransaction, + child: const Text('Increment as transaction'), + ), + ListTile( + leading: Checkbox( + onChanged: _setAnchorToBottom, + value: _anchorToBottom, ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, + title: const Text('Anchor to bottom'), + ), + Flexible( + child: FirebaseAnimatedList( + key: ValueKey(_anchorToBottom), + query: _messagesRef, + reverse: _anchorToBottom, + itemBuilder: (context, snapshot, animation, index) { + return SizeTransition( + sizeFactor: animation, + child: ListTile( + trailing: IconButton( + onPressed: () => _deleteMessage(snapshot), + icon: const Icon(Icons.delete), + ), + title: Text('$index: ${snapshot.value.toString()}'), + ), + ); + }, ), - ], - ), + ), + ], ), floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, + onPressed: _increment, tooltip: 'Increment', child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } } diff --git a/packages/firebase_database/example/pubspec.yaml b/packages/firebase_database/example/pubspec.yaml index 4186081..5db0b4e 100644 --- a/packages/firebase_database/example/pubspec.yaml +++ b/packages/firebase_database/example/pubspec.yaml @@ -8,15 +8,14 @@ dependencies: firebase_core: 2.4.1 firebase_core_tizen: ^1.0.0 firebase_database: 10.0.9 - firebase_database_tizen: ^0.1.0 + firebase_database_tizen: + path: .. flutter: sdk: flutter dependency_overrides: firebase_core_tizen: path: ../../firebase_core - firebase_database_tizen: - path: .. dev_dependencies: flutter_test: From a512be0e2a5adeb4827350efe67df0c71fa7d975 Mon Sep 17 00:00:00 2001 From: Daeyeon Jeong Date: Thu, 3 Aug 2023 17:49:58 +0900 Subject: [PATCH 5/5] fixup! fixup! fixup! fixup! feat(database): add initial firebase database Signed-off-by: Daeyeon Jeong --- packages/firebase_database/example/analysis_options.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/firebase_database/example/analysis_options.yaml b/packages/firebase_database/example/analysis_options.yaml index a1fbb31..a174f61 100644 --- a/packages/firebase_database/example/analysis_options.yaml +++ b/packages/firebase_database/example/analysis_options.yaml @@ -2,4 +2,9 @@ # Use of this source code is governed by a BSD-style license that can be # in the LICENSE file. -include: ../analysis_options.yaml +include: ../../../analysis_options.yaml +linter: + rules: + avoid_print: false + depend_on_referenced_packages: false + library_private_types_in_public_api: false