Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Firestore platform interface (v2) #1633

Closed
wants to merge 24 commits into from

Conversation

ditman
Copy link
Contributor

@ditman ditman commented Dec 13, 2019

Description

Continuation of the WIP for the cloud_firestore federation; platform interface + core plugin changes.

This v2 attempts to address the concerns raised in #1568.

This is still incomplete, but I've implemented the minimal scaffolding code so I can run the firestore core plugin tests on top of the new implementation. I'm using that test output to track progress, and ensure feature parity with the old version.

Missing things:

  • Actual MethodChannel implementations for most of the functionality; for now:
00:01 +9 -23: Firestore Query document pagination FieldPath assertions [E]                                                                                                              
  UnimplementedError: DocumentReferencePlatform::get() is not implemented
  package:cloud_firestore_platform_interface/src/interfaces/document_reference_platform.dart 59:5  DocumentReferencePlatform.get
  package:cloud_firestore/src/document_reference.dart 77:90                                        DocumentReference.get
  cloud_firestore_test.dart 1113:67                                                                main.<fn>.<fn>.<fn>
  • 00:02 +23 -23: Some tests failed.
  • Tests in the cloud_firestore_platform_interface package.
  • Document all public members
  • Actual dartfmt and flutter analyze

Related Issues

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • If the pull request affects only one plugin, the PR title starts with the name of the plugin in brackets (e.g. [cloud_firestore])
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
  • I updated/added relevant documentation (doc comments with ///).
  • The analyzer (flutter analyze) does not report any problems on my PR.
  • I read and followed the Flutter Style Guide.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (please indicate a breaking change in CHANGELOG.md and increment major revision).
  • No, this is not a breaking change.

Copy link

@harryterkelsen harryterkelsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial comments as this is still WIP


class MethodChannelDocumentReference extends DocumentReferencePlatform {
MethodChannelDocumentReference() {
channel.setMethodCallHandler(_callHandler);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like you are calling setMethodCallHandler multiple times. I think this will cause the previous handler for that channel to be overwritten.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, though each instance of the platform has its own copy of the MethodChannel (I HOPE)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hterkelsen you were right, even across different instances of a MethodChannel, only the LAST call to setMethodCallHandler seems to be called.

@mklim said: "There's only one handler per channel. A way to thin the Handler code is to actually move the implementation of the methods into other classes, and write the Handler itself just to plumb from method call and response to the business logic and back."

}

@visibleForTesting
static const MethodChannel channel = MethodChannel(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the original implementation I see that the channel is initialized with FirestoreMessageCodec. You probably need to move FirestoreMessageCodec to this package and make sure the channels are initialized with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think I didn't realize this. I'll get this done!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my, this codec complicates things 😭

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of how the Codec operates (it basically uses the frontend of the plugin to decode some data types), I changed the initialization code of the MethodChannel Implementations to allow to pass a codec in; that way we can pass a codec with access to "userland" from the plugin, without having to migrate EVERY data type defined in the plugin (I tried, it was painful, and still didn't solve my issue).

This is the problematic bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above probably explains why there's so much "static" code, and initialization guards in different places of the plugin; since we're creating new instances of the plugin all the time the message codec needs to hydrate a serialized DocumentReference!

Fixing this is very involved; we may be able to inject a factory function so the codec doesn't need to create instances in there, but maybe this particular part of the plugin could benefit from some extra design work (I didn't find any other place where layers are pierced like this, so far)

Copy link
Contributor

@collinjackson collinjackson Dec 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is hydrating a serialized DocumentReference expensive? It's not clear to me how much overhead is involved in that, compared to the general overhead of moving serialized snapshots in and out of the Dart VM.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it is not very expensive because:

  • There's a bunch of init guards/static code that prevents multiple initialization
  • We hydrate the moment we see a DcoumentReference that needs to be hydrated.

However, logic-wise, it is expensive, because the deserialization needs to be able to basically instantiate a whole new plugin.

If we want to break that up, we'd probably need to hoist all the data types to the platform interface. At that moment, to maintain current feature parity, we'd need hydration on getters (or similar). The cost of that hydration would depend on the complexity of the returned data.

(Or maybe there's something I'm not seeing, there's a chance I've been focusing on this code for too long already :P)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

class PlatformWriteBatch {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs. Also I think it would be clearer if this was named PlatformWriteBatchHandle

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, docs are missing pretty much everywhere. I'm giving myself some time before I migrate this to add *Handle at the end, but I agree it's clearer.

}

// TODO: this needs to be more precise
class PlatformQuerySnapshot {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably model this class on https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/QuerySnapshot

I would remove asMap() as well, and make the app-facing plugin use the typed data

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've been avoiding this type for a while. I have a suspicion that the data that the backend sends back through the channel to the frontend is not entirely identical to the one documented, so I'll model this as I go (I might be wrong)

@ditman
Copy link
Contributor Author

ditman commented Dec 14, 2019

Some more tests are passing now; after adding the Transaction implementation:

00:01 +27 -19: Some tests failed.

This way, all MethodChannel implementations can share the same
MethodChannel instance, and register additional method handlers after
the class has been created.
Also, simplify test initialization now that all MethodChannel
implementations share the same instance.
@ditman
Copy link
Contributor Author

ditman commented Dec 16, 2019

Added Query implementation:

00:02 +36 -10: Some tests failed.

@ditman
Copy link
Contributor Author

ditman commented Dec 17, 2019

Added DocumentReference implementation.

00:01 +46: All tests passed!

@ditman
Copy link
Contributor Author

ditman commented Dec 17, 2019

(Currently fixing dartfmt and analyzer)

_instance = instance;
}

// Actual interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am leaning towards removing this non-Dartdoc comment (in all PlatformInterface subclasses) since people will be follow the example of this plugin and it clutters up the class a bit. If the code is self-documenting only Dartdoc comments will be necessary.


typedef _MultiMethodChannelHandler(MethodCall call);

class MultiMethodChannel extends MethodChannel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should move this into a package (in the Flutter SDK? in the plugin_platform_interface package?) since it will be useful for firebase_database and firebase_storage. /cc @amirh

@@ -15,6 +15,8 @@ flutter:
pluginClass: FLTCloudFirestorePlugin

dependencies:
cloud_firestore_platform_interface:
path: ../cloud_firestore_platform_interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should eventually be pointing to a hosted version (probably by splitting this into two PRs)

'transactionTimeout': timeout.inMilliseconds
});

// Wrap the user-supplied [TransactionHandler] into something that can be passed to the Platform implementation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Wrap the user-supplied [TransactionHandler] into something that can be passed to the Platform implementation.
// Wrap the user-supplied [TransactionHandler] into something that can be passed to the platform implementation.

};

// Ensure the MethodChannel platforms are initialized
Firestore.platform;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels unintuitive that this has side effects. What do you think about doing a method instead?

// Ensure the MethodChannel platforms are initialized
Firestore.platform;

// All the MethodChannel implementation share the same channel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// All the MethodChannel implementation share the same channel
// All the MethodChannel implementations share the same channel


/// The platform instance that talks to the native side of the plugin.
@visibleForTesting
static final FirestorePlatform platform = FirestorePlatform.instance ?? (FirestorePlatform.instance = MethodChannelFirestore(FirestoreMessageCodec()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit unintuitive to be doing the assignment like this.

@ditman
Copy link
Contributor Author

ditman commented Dec 20, 2019

(Closing in favor of #1670)

@ditman ditman closed this Dec 20, 2019
@ditman ditman deleted the firestore_platform_interface branch January 14, 2020 01:45
@firebase firebase locked and limited conversation to collaborators Aug 5, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants