-
Notifications
You must be signed in to change notification settings - Fork 257
feat(amplify_storage_s3): UploadFile and GetUrl #15
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
Conversation
packages/amplify_core_plugin_interface/lib/amplify_storage_category.dart
Outdated
Show resolved
Hide resolved
packages/amplify_storage_plugin_interface/lib/src/GetUrl/GetUrlOptions.dart
Show resolved
Hide resolved
packages/amplify_storage_plugin_interface/lib/src/GetUrl/GetUrlOptions.dart
Outdated
Show resolved
Hide resolved
packages/amplify_storage_plugin_interface/lib/src/Storage/StorageOptions.dart
Outdated
Show resolved
Hide resolved
packages/amplify_storage_plugin_interface/lib/src/UploadFile/UploadFileOptions.dart
Outdated
Show resolved
Hide resolved
// import 'package:flutter_test/flutter_test.dart'; | ||
|
||
// import 'package:amplify_storage_plugin_interface/amplify_storage_plugin_interface.dart'; | ||
|
||
// void main() { | ||
// test('adds one to input values', () { | ||
// final calculator = Calculator(); | ||
// expect(calculator.addOne(2), 3); | ||
// expect(calculator.addOne(-7), -6); | ||
// expect(calculator.addOne(0), 1); | ||
// expect(() => calculator.addOne(null), throwsNoSuchMethodError); | ||
// }); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably should delete the file if not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, delete this whole test directory if we don't have actual tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
...android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageOperations.kt
Outdated
Show resolved
Hide resolved
...3/android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageS3Plugin.kt
Outdated
Show resolved
Hide resolved
...3/android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageS3Plugin.kt
Outdated
Show resolved
Hide resolved
...3/android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageS3Plugin.kt
Outdated
Show resolved
Hide resolved
...3/android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageS3Plugin.kt
Outdated
Show resolved
Hide resolved
...droid/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/types/FlutterGetUrlRequest.kt
Outdated
Show resolved
Hide resolved
...droid/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/types/FlutterGetUrlRequest.kt
Show resolved
Hide resolved
...d/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/types/FlutterUploadFileRequest.kt
Outdated
Show resolved
Hide resolved
...android/src/main/kotlin/com/amazonaws/amplify/amplify_storage_s3/AmplifyStorageOperations.kt
Outdated
Show resolved
Hide resolved
let req = FlutterGetUrlRequest(request: request) | ||
print("GetUrlRequest.Options object is: \(String(describing: req.options))") | ||
_ = Amplify.Storage.getURL(key: req.key, | ||
options: nil, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not req.options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Draft PR. Lot of this PR is WIP but I wanted you to get your eyes on it before you leave.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. I didn't see a Todo on this one that's why left a comment to keep track
var options: StorageUploadFileRequest.Options? = nil | ||
init(request: Dictionary<String, AnyObject>) { | ||
self.key = request["key"] as! String | ||
self.file = NSURL(fileURLWithPath: request["path"] as! String) as URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be validated that the path is valid? in the isValid()
before init
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I can add that to isValid()
} | ||
} | ||
|
||
Future<UploadFileResponse> uploadFile({@required UploadFileRequest request}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that this is where a subsequent PR will allow discrete parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea -- where did you guys land with this? In the design review, we had proposed to do a 1 pager weighing pros and cons. Were there any cons to "exploding" the common args into the method signature? Or what's the status?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are doing it. For now we are just creating the request instance inside the method and exposing an interface with named parameters to the customer. I dont have that in this PR, it will be a followup.
No real downside to that, and definite upside of better customer experience.
@@ -0,0 +1,6 @@ | |||
class GetUrlResponse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that closing on this will be a subsequent PR prior to P0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ @haverchuck Closing on what?
Map<String, dynamic> serializeAsMap() { | ||
final Map<String, dynamic> optionsMap = <String, dynamic>{}; | ||
if (accessLevel != null) { | ||
optionsMap["accessLevel"] = describeEnum(accessLevel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
@@ -0,0 +1,10 @@ | |||
export 'Storage/StorageAccessLevel.dart'; | |||
export 'Storage/StorageOptions.dart'; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: as these type files grow, it can be helpful to create little comment headings (IMO)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping diligent about the use of whitespace also does work. The comments could end up like:
// Do the needful
export 'do'
export 'the'
export 'needful'
Or, I guess you could document the grouping strategy, once, at the top of the document. That could be frugal.
|
||
fun uploadFile(@NonNull flutterResult: MethodChannel.Result, @NonNull request: HashMap<String, *>, mainActivity: Activity?) { | ||
if (FlutterUploadFileRequest.isValid(request)) { | ||
Log.i("AmplifyFlutter", "uploadFile request:" + request.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be helpful to use amplify-android's logging utility.
@@ -0,0 +1,153 @@ | |||
PODS: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should not be checked in.
Pod::Spec.new do |s| | ||
s.name = 'amplify_storage_s3' | ||
s.version = '0.0.1' | ||
s.summary = 'A new flutter plugin project.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to update this boilerplate.
|
||
environment: | ||
sdk: ">=2.7.0 <3.0.0" | ||
flutter: ">=1.10.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to bump this to >=1.20.0 and retest
@@ -0,0 +1,23 @@ | |||
// import 'package:flutter/services.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that we will have tests here prior to p0; ditto for android unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some minor requests for revisions.
plugin: | ||
platforms: | ||
android: | ||
package: com.amazonaws.amplify.amplify_storage_s3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
com.amplifyframework.flutter.storage.s3
?
class StorageCategory { | ||
const StorageCategory(); | ||
|
||
static List<StoragePluginInterface> plugins = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In iOS and Android, the category and plugin both extend a common interface. This ensures that the customer APIs are fully addressed by the plugin developer. How come you all decided not to pursue this pattern for Flutter?
} | ||
} | ||
|
||
Future<UploadFileResponse> uploadFile({@required UploadFileRequest request}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea -- where did you guys land with this? In the design review, we had proposed to do a 1 pager weighing pros and cons. Were there any cons to "exploding" the common args into the method signature? Or what's the status?
|
||
Future<GetUrlResponse> getUrl({@required GetUrlRequest request}) { | ||
/// call `getUrl` on the plugin | ||
return plugins[0].getUrl(request: request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android and iOS have a method for this purpose, like getSelectedPlugin()
. It does range checking on the list, and if there is not any plugin, will return a friendly message: "Kind friend, please consider the opportunity to add a plugin." etc. A little better than "0 not in range."
@@ -0,0 +1,3 @@ | |||
## [0.0.1] - TODO: Add release date. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just delete this file until you have real content for it.
} | ||
) | ||
} catch (e: Exception) { | ||
prepareError(flutterResult, "ERROR_ENCOUNTERED_IN_GET_URL_OPERATION", e.localizedMessage, "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These screaming snake strings are weird. They look like they want to be constants in the language, but yet they are strings. Is the intention to show these to a user? If so, style these appropriately for consumption as natural langauge.
An error was enountered in the GetUrl operation.
instead of
ERROR_ENCOUNTERED_IN_GET_URL_OPERATION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. I Intend to make them constants. There is a TODO uptop, line 17.
private fun prepareGetUrlResponse(@NonNull flutterResult: MethodChannel.Result, result: StorageGetUrlResult) { | ||
Handler (Looper.getMainLooper()).post { | ||
Log.i("AmplifyFlutter", "Successfully got Url: " + result.url.toString()) | ||
val response: HashMap<String, String> = HashMap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Liskov Principle.
Map
on LHS. Implementation type on RHS. Same comment for entire code base.
} | ||
} | ||
|
||
//TODO: Move to Error or Util Class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, well, maybe. Right now, you have one method inside of a companion object, and then this method is at global scope. Shouldn't they at least be the same? Why is this not in the companion object, or why is the companion method not at global scope?
Handler (Looper.getMainLooper()).post { | ||
Log.i("AmplifyFlutter", "Error: " + localizedError + msg); | ||
var errorDetails: HashMap<String, Any> = HashMap(); | ||
errorDetails.put("PLATFORM_EXCEPTIONS", mapOf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone will be expected to reliably read this key back out, don't use an inline string constant. Instead, wearhouse this value as a public constant somewhere. Perhaps in the companion object, perhaps in a separate enum somewhere, perhaps as a StringDef. But, as a consumer I'm going to do:
errorDetails.get("PLATFORM_EXCEPTIONSSS") // Ooops, this doesn't work at runtime and I get confused because my project has thousands of lines of code -- n^2 complexity to this problem, the more runtime errors I ahve instead of compile time
val key: String = request["key"] as String | ||
val options: StorageGetUrlOptions = setOptions(request) | ||
|
||
private fun setOptions(request: HashMap<String, *>): StorageGetUrlOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map
, Liskov
result(FlutterMethodNotImplemented) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
return nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
private func setOptions(request: Dictionary<String, AnyObject>) -> StorageUploadFileRequest.Options? { | ||
|
||
if(request["options"] != nil) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(request["options"] != nil) { | |
if (request["options"] != nil) { |
} | ||
|
||
private func setOptions(request: Dictionary<String, AnyObject>) -> StorageUploadFileRequest.Options? { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
static func isValid(request: Dictionary<String, AnyObject>) -> Bool { | ||
var valid: Bool = true; | ||
if(!(request["key"] != nil && request["key"] is String)){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if(!(request["key"] != nil && request["key"] is String)){ | |
if (!(request["key"] != nil && request["key"] is String)) { |
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" | ||
|
||
android { | ||
compileSdkVersion 28 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd target 30. Make sure target and compile versions match.
val file: File = File(request["path"] as String) | ||
val options: StorageUploadFileOptions = setOptions(request) | ||
|
||
private fun setOptions(request: HashMap<String, *>): StorageUploadFileOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use just the Map
interface, for all method specs. HashMap
only gets used when you're creating an instance.
Same comment for entire code base, not just this file.
val accessLevel: StorageAccessLevel? = StorageAccessLevel.values().find { it.name == accessLevelStringOption.toUpperCase() } | ||
options.accessLevel(accessLevel) | ||
} | ||
"targetIdentityId" -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you get these strings into an enum? Using string literals like this is error prone, if you'll use them in more than one place.
} | ||
} | ||
|
||
fun getUrl(@NonNull flutterResult: MethodChannel.Result, @NonNull request: HashMap<String, *>, mainActivity: Activity?) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Activity from the method spec.
import io.flutter.plugin.common.PluginRegistry.Registrar | ||
|
||
|
||
class AmplifyStorageS3Plugin : FlutterPlugin, ActivityAware, MethodCallHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to implement ActivityAware
, or are you doing this because you wanted to use a mainActivity
to exploit its runOnUiThread
?
If at all possible, don't mess with an Activity, at all. You can run on the main thread via Handler(Looper.getMainLooper())
. But even for that, make sure you have to. You have to, for UI operations. That's about it.
Created a PR off a new branch where I addressed most of the above comments along with other improvements. Closing this draft PR. |
Description of changes:
Draft PR that adds support for UploadFile and GetUrl functionality for Amplify Flutter.
Todo:
Error handling improvements, tests and other refinement mentioned in TODOs
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.