Firebase Storage Component Registration#277
Conversation
c02e1b2 to
7ba9359
Compare
vkryachko
left a comment
There was a problem hiding this comment.
@schmidt-sebastian Awesome! Thanks for picking it up.
One important thing is that we also need storage to stop integrating with Auth via FirebaseApp(I think it uses the deprecated #getToken(), but maybe something else as well) and start using firebase-auth-interop's InternalAuthProvider like e.g. firestore does:
Feel free to punt it to a separate PR.
| } | ||
| checkNotNull(app, "Provided FirebaseApp must not be null."); | ||
| FirebaseStorageComponent component = app.get(FirebaseStorageComponent.class); | ||
| checkNotNull(component, "Firestore component is not present."); |
| } | ||
|
|
||
| @NonNull | ||
| static FirebaseStorage newInstance(@Nullable String bucketName, @NonNull FirebaseApp app) { |
There was a problem hiding this comment.
Do you actually need this method? Would it make sense to make the constructor package-private and call it directly in the Component? As a side-effect I think it will also make it possible to mock FirebaseStorage.
There was a problem hiding this comment.
No, we don't need it. I was trying to follow the precedent set by Firestore. Inlined.
|
|
||
| class FirebaseStorageComponent { | ||
| /** A static map from storage buckets to FirebaseStorage instances. */ | ||
| private static final Map<String, FirebaseStorage> instances = new HashMap<>(); |
There was a problem hiding this comment.
Note this should not be static, otherwise the instances will be shared between all FirebaseApps.
There was a problem hiding this comment.
Oh yes, that makes sense. Thanks for catching this.
| return storage; | ||
| } | ||
|
|
||
| /** @hide */ |
There was a problem hiding this comment.
I think @hide is not needed for non-public classes/symbols
There was a problem hiding this comment.
The original method was public. I have removed it now that I reduced the visibility.
| this.app = app; | ||
| } | ||
|
|
||
| /** Provides instances of Firestore for given database names. */ |
ed00904 to
339880f
Compare
schmidt-sebastian
left a comment
There was a problem hiding this comment.
Updated the PR. Most of the changes now are related to the switch to InternalAuthTokenProvider,
| } | ||
|
|
||
| @NonNull | ||
| static FirebaseStorage newInstance(@Nullable String bucketName, @NonNull FirebaseApp app) { |
There was a problem hiding this comment.
No, we don't need it. I was trying to follow the precedent set by Firestore. Inlined.
| } | ||
| checkNotNull(app, "Provided FirebaseApp must not be null."); | ||
| FirebaseStorageComponent component = app.get(FirebaseStorageComponent.class); | ||
| checkNotNull(component, "Firestore component is not present."); |
|
|
||
| class FirebaseStorageComponent { | ||
| /** A static map from storage buckets to FirebaseStorage instances. */ | ||
| private static final Map<String, FirebaseStorage> instances = new HashMap<>(); |
There was a problem hiding this comment.
Oh yes, that makes sense. Thanks for catching this.
| this.app = app; | ||
| } | ||
|
|
||
| /** Provides instances of Firestore for given database names. */ |
| return storage; | ||
| } | ||
|
|
||
| /** @hide */ |
There was a problem hiding this comment.
The original method was public. I have removed it now that I reduced the visibility.
339880f to
4039ddd
Compare
|
/test connected-check-changed |
| public static FirebaseStorage getInstance(@NonNull FirebaseApp app, @NonNull String url) { | ||
| // noinspection ConstantConditions | ||
| Preconditions.checkArgument(app != null, "Null is not a valid value for the FirebaseApp."); | ||
| Preconditions.checkArgument( |
There was a problem hiding this comment.
optional nit: elsewhere you switched to staticly imported checks, might make sense to do the same throughout for consistency.
There was a problem hiding this comment.
The rest of the SDK doesn't use static imports, so I changed this file to use the qualified import checkNotNull.
| return Arrays.asList( | ||
| Component.builder(FirebaseStorageComponent.class) | ||
| .add(Dependency.required(FirebaseApp.class)) | ||
| .add(Dependency.optional(InternalAuthProvider.class)) |
There was a problem hiding this comment.
With my limited understanding of storage it looks like InternalAuthProvider is not needed right away(when storage initializes) unlike in Firestore, but only used in response to user calls to downloads and what not. If that is the case consider switching to Dependency.optionalProvider(InternalAuthProvider.class), what it will do is defer initialization of InternalAuthProvider until it is needed as well as prevent potential dependency cycles in the future.
That said, currently the only implementor of InternalAuthProvider is FirebaseAuth which is eager, so deferred initialization will not apply, but it seems a relatively easy change to do(especially if the Provider<T> is hidden behind storage.getAuthProvider()) to bulletproof storage for the future. wdyt?
There was a problem hiding this comment.
Your understanding is correct. We only fetch a token once a user requests a file upload or download, so we don't need the AuthProvider until later. The change for this was relatively minor, so I updated this PR.
BTW, if wasn't immediately obvious to me whether optionalProvider makes Provider nullable or whether it makes Provider.get() nullable. I think it's the former (from looking through the code and playing with it). Maybe we can document this?
| private final AdaptiveStreamBuffer mStreamBuffer; | ||
| // Active, current mutable state. | ||
| private final AtomicLong mBytesUploaded = new AtomicLong(0); | ||
| @Nullable final InternalAuthProvider mAuthProvider; |
There was a problem hiding this comment.
Yes. FIxed
schmidt-sebastian
left a comment
There was a problem hiding this comment.
Updated the PR. My test app is able to authenticate with these changes.
| public static FirebaseStorage getInstance(@NonNull FirebaseApp app, @NonNull String url) { | ||
| // noinspection ConstantConditions | ||
| Preconditions.checkArgument(app != null, "Null is not a valid value for the FirebaseApp."); | ||
| Preconditions.checkArgument( |
There was a problem hiding this comment.
The rest of the SDK doesn't use static imports, so I changed this file to use the qualified import checkNotNull.
| return Arrays.asList( | ||
| Component.builder(FirebaseStorageComponent.class) | ||
| .add(Dependency.required(FirebaseApp.class)) | ||
| .add(Dependency.optional(InternalAuthProvider.class)) |
There was a problem hiding this comment.
Your understanding is correct. We only fetch a token once a user requests a file upload or download, so we don't need the AuthProvider until later. The change for this was relatively minor, so I updated this PR.
BTW, if wasn't immediately obvious to me whether optionalProvider makes Provider nullable or whether it makes Provider.get() nullable. I think it's the former (from looking through the code and playing with it). Maybe we can document this?
| private final AdaptiveStreamBuffer mStreamBuffer; | ||
| // Active, current mutable state. | ||
| private final AtomicLong mBytesUploaded = new AtomicLong(0); | ||
| @Nullable final InternalAuthProvider mAuthProvider; |
There was a problem hiding this comment.
Yes. FIxed
|
/test connected-check-changed |
1 similar comment
|
/test connected-check-changed |
vkryachko
left a comment
There was a problem hiding this comment.
LGTM
Looks like we have a legit failure in firebase-database-collection whose minSdkVersion is less than min version of playservices-base, I feel like this has alway been the case but now it suddently started failing(I'll take a look at and fix it)
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) | ||
| public class StorageRegistrar implements ComponentRegistrar { | ||
| @Override | ||
| @Keep |
There was a problem hiding this comment.
nit: no need to @Keep this method, just the class is enough
|
/retest |
This definitely falls under the umbrella "I don't know what I am doing". But here we go...
This follows the pattern in Firestore to register the component for Firebase Storage. It changes FirebaseStorage to only ever work with a single app, but keeps the option to get different instances for different storage buckets.
I also added
getInstanceImplas a valid name for the FirebaseApp.get() call, as I didn't want to add yet anothergetInstance()overload (which would result in an ambiguous null cast).So what does everyone think? :)