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

[firebase_core] Add platform interface #1324

Merged
merged 12 commits into from
Nov 14, 2019
Merged

[firebase_core] Add platform interface #1324

merged 12 commits into from
Nov 14, 2019

Conversation

harryterkelsen
Copy link

Description

Adds a platform interface for firebase_core.

NOTE: This contains a copy of FirebaseOptions. In a follow-up CL which migrates firebase_core to use this platform interface, I will delete FirebaseOptions from package:firebase_core and export it from firebase_core. I moved it here because the platform interface should be typed, and prefer to not use Map for data.

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.

@harryterkelsen
Copy link
Author

/cc @ditman

@harryterkelsen
Copy link
Author

Just merged in the latest Android lifecycle dependency change. PTAL and I will start on migrating package:firebase_core to use this.

@harryterkelsen
Copy link
Author

For context:

@harryterkelsen
Copy link
Author

Friendly ping. This is a pre-req for landing Web support for firebase_core

Copy link
Contributor

@ditman ditman left a comment

Choose a reason for hiding this comment

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

LGTM, but I'm not the owner so let's wait for their approval. All my comments are minor nits/typos so feel free to disregard.

script/check_publish.sh Show resolved Hide resolved
import 'firebase_options.dart';

/// A data class storing the name and options of a Firebase app.
class FirebaseAppData {
Copy link
Contributor

@collinjackson collinjackson Nov 11, 2019

Choose a reason for hiding this comment

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

This relates to #1392 which @kroikie just landed.

Generally in Flutterfire we try to match the native Firebase APIs as closely as we can while remaining idiomatic to the target platform. There's no equivalent to the FirebaseAppData class in the native Firebase API. There's just FirebaseApp and our Dart version of it exposes a Future<FirebaseOptions> options getter.

The reason why we put an asynchronous options on firebase_core's FirebaseApp is that we wanted the developer to be able to say FirebaseApp.instance and start using that app immediately without calling await.

It seems odd, although not necessarily wrong to have a class FirebaseOptions that matches the naming in the app-facing API exactly and is designed to be exported, but then have another class FirebaseAppData that serves a similar function to FirebaseApp but has a slightly different class behavior and can't be exported. It seems that the main difference is that FirebaseOptions is a pure data class whereas FirebaseApp has some behavior.

It seems like for every class we have the following options. All of them avoid exposing nonstandard classes (FirebaseAppData) to the end developer.

  1. We could write the platform interface class in a way that can be exported and reused (so firebase_core and firebase_core_platform_interface share both the same class). This approach would match what's going on with FirebaseOptions but makes it harder to make breaking changes in firebase_core because they might also break firebase_core_platform_interface.
  2. Use Dart namespaces to disambiguate app-facing classes and platform interface classes, e.g. having a class called FirebaseApp but don't require it to match firebase_core's FirebaseApp. The same could apply to FirebaseOptions. It could be confusing to have two identically named classes that are subtly different, but it makes it clear that both classes refer to the same native SDK concept on different layers. One of them could extend the other without polluting public API with classes that don't have a native SDK equivalent.
  3. We can have the platform interface class names be visually distinguishable from the class name in the the app-facing package (e.g. FirebaseAppData or PlatformFirebaseApp). This might make make the code more readable within the app-facing package because it's obvious that the class is a more primitive representation of an app-facing class. However, it isn't possible to extend the platform class without exposing a platform implementation base class to the end developer.

Right now you're doing Option 1 for FirebaseOptions and Option 3 for FirebaseApp. Starting off with Option 1 and then moving to Option 2 or 3 later is possible, although here it would force the platform interface to use the same asynchronous API for options.

Here's a sketch of what it might look like to do option 3 with FirebaseApp (i.e. PlatformFirebaseApp instead of FirebaseAppData).

export 'firebase_core_platform_implementation.dart' show FirebaseOptions; 

class FirebaseApp {
  static Map<String, FirebaseApp> _apps = <String, FirebaseApp>{};

  /// Retrieves a [FirebaseApp] that was previously configured using [configure].
  ///
  /// If no name is provided, the default is used.
  factory FirebaseApp.instanceFor({ String name }) {
    if (name == null) {
      return FirebaseApp.instance;
    }
    String app = _apps[name];
    assert(app != null);
    return app;
  }

  /// Returns the default (first initialized) instance of the FirebaseApp.
  static final FirebaseApp instance = FirebaseApp(name: defaultAppName);

  @visibleForTesting
  FirebaseApp({ @required String this.name });

  /// The name of this Firebase app.
  final String name;

  /// The options that this app was configured with.
  Future<FirebaseOptions> get options {
    PlatformFirebaseApp platformApp = await FirebaseCorePlatform.instance.appNamed(name);
    return platformApp.options;
  }

  /// Configures the app named [name] with the given [options].
  ///
  /// Returns a previously configured app if there is one.
  /// Reconfiguring an app is not supported.
  static Future<FirebaseApp> configure({
    @required String name,
    @required FirebaseOptions options,
  }) async {
    if (options != null) {
      await FirebaseCorePlatform.instance.configure(name, options);
    }
    PlatformFirebaseApp platformApp = await FirebaseCorePlatform.instance.appNamed(name);
    FirebaseApp app = FirebaseApp._(platformApp);
    _apps[name] = app;
    return app;
  }
}

In the platform interface:

class PlatformFirebaseApp {
  PlatformFirebaseApp(this.name, this.options);

  /// The name of this Firebase app.
  final String name;

  /// The options that this app was configured with.
  final FirebaseOptions options;

  @override
  bool operator ==(dynamic other) {
    if (identical(this, other)) return true;
    if (other is! FirebaseAppData) return false;
    return other.name == name && other.options == options;
  }

  @override
  int get hashCode => hash2(name, options);

  @override
  String toString() => '$FirebaseApp($name)';
}

abstract class FirebaseCorePlatform {
  /// Returns the data for the Firebase app with the given [name].
  ///
  /// If there is no such app, returns null.
  Future<FirebaseApp> appNamed(String name);

  /// Configures the app named [name] with the given [options].
  Future<FirebaseApp> configure(String name, FirebaseOptions options);
}

@harryterkelsen
Copy link
Author

I renamed FirebaseAppData to PlatformFirebaseApp, PTAL

Copy link
Contributor

@collinjackson collinjackson left a comment

Choose a reason for hiding this comment

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

LGTM, you'll want to fix the formatting issue here:

https://github.com/FirebaseExtended/flutterfire/runs/300074657

Merging the latest master will get you #1404 which should fix the other build failure.

@harryterkelsen
Copy link
Author

Thanks, @ditman. I'm pretty sure that's what I'm seeing here. I'll make a PR that applies that change to FlutterFire

@ditman ditman merged commit 4900968 into firebase:master Nov 14, 2019
@ditman
Copy link
Contributor

ditman commented Nov 14, 2019

Everything was green and approved, so I merged this!

@ditman
Copy link
Contributor

ditman commented Nov 14, 2019

(I've also added @hterkelsen as collaborator of the repo, @collinjackson)

thatfiredev pushed a commit to thatfiredev/flutterfire that referenced this pull request Nov 26, 2019
* bump core version to 0.4.1+4 (firebase#1395)

* Remove deprecated firebase-core dependency (firebase#1417)

* Remove deprecated firebase-core dependency

* [firebase_auth] getIdToken use actual refresh value instead of checking if object exists (firebase#1334)

* getIdToken use actual refresh value instead of checking if object exists

* getIdToken refresh integration test

* update xcode to v11 (firebase#1420)

* Update FirebaseApp creation in tests (firebase#1406)

* [firebase_database] Support v2 android embedder. (firebase#287)

* [firebase_remote_config] Support v2 android embedder. (firebase#282)

* [CI] Add version to example apps (firebase#1426)

* Add hardcode version to example apps

* [firebase_core] Add platform interface (firebase#1324)

* Move firebase_core to firebase_core/firebase_core
* Add firebase_core_platform_interface package
* Update cirrus scripts and documentation

* [firebase_dynamic_links] Add support of expanding from short links (firebase#1381)

* Fixes strict compilation errors. (firebase#1331)

* Upgrade crashlytics to v2 plugin API (firebase#1370)

* Upgraded Crashlytics to v2 of Flutter Plugins.

* Format documentation lists (See also & Errors) correctly (firebase#298)

Fix documentation list format.

* [firebase_auth] Fix NoSuchMethodError exception in `reauthenticateWithCredential` (firebase#237)

* [firebase_auth] Fix NoSuchMethodError exception

Fix NoSuchMethodError exception when calling FirebaseUser.reauthenticateWithCredential

* add integration test

* Fix analyzer warnings

Co-Authored-By: Collin Jackson <jackson@google.com>

* [cloud_firestore] [firebase_performance] fix analyzer warnings (firebase#1453)

* fix analyzer warnings

* [CI] Skip flaky firebase_performance driver tests (firebase#1452)

* Skip flaky firebase_performance driver tests

* [firebase_auth] Added missing Exception to the reauthenticateWithCredential docs (firebase#1448) (firebase#1449)

* Added missing Exception to the reauthenticateWithCredential docs (firebase#1448)

* [firebase_remote_config] Bumps Android Firebase dependency to 19.0.3 (firebase#1443)

* Bump AGP, Gradle & Google Services Plugin

* Bumps dependency to Firebase Config 19.0.3

* Replaces a deprecated method usage with the updated version

* [firebase_messaging] Use UNUserNotificationCenter for ios 10+ (firebase#121)

* Use UNUserNotificationCenter for ios 10+

* Add swift documentation

* Move http test to README

* Update CONTRIBUTING.md (firebase#1473)

Removes obsolete recommendations about removing mockito (which is now used in testing federated plugins).

* [firebase_core] Migrate to platform_interface (firebase#1472)

* [firebase_messaging] Add an ArgumentError when passing invalid background message (firebase#252)

* [firebase_dynamic_links] support v2 embedding (firebase#1372)

* [cloud_firestore] Fix test that used FirebaseApp.channel (firebase#1476)

* [cloud_firestore] Fix test that used FirebaseApp.channel

* dartfmt

* bump version

* Fixed dynamic issue

* Fixed dynamic issue

* FIxed Object to list of object mapping

* Fixed text result expectation

* Reverted some changes

* Satisfying formatter

* Added android x to pass the test

* Fixed changed log auto formatting
@firebase firebase locked and limited conversation to collaborators Aug 8, 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.

4 participants