I am using AWS S3 as a backend for sound file storage in an iOS app. I can upload sound files to the bucket as I wish, but I am having trouble to make the download work.
I was first hoping to make things work using PFFile, but since I did not succeed as expected, I did some research and had the impression that using Cognito was the way to go. Since it is my firstime to use it, I may well be missing some important part.
Here is the application:didFinishLaunchingWithOptions method.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
..........
// For the AWSS3 configuration:
let poolID = "us-east-1:123.........",
credentialProvider = AWSCognitoCredentialsProvider(regionType: .USEast1,
identityPoolId: poolID)
let awsConfig = AWSServiceConfiguration(region: .APNortheast1,
credentialsProvider: credentialProvider)
AWSServiceManager.default().defaultServiceConfiguration = awsConfig
return true
}
At some point I have this code executed:
transferManager = AWSS3TransferManager.default()
For downloading the sound file, this code is run:
let soundFileName = ....,
downloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(soundFileName),
downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest?.bucket = "theappbucket"
downloadRequest?.key = soundFileName
downloadRequest?.downloadingFileURL = downloadFileURL
transferManager.download(downloadRequest!).continueWith(
executor: AWSExecutor.mainThread(),
block: {
(task:AWSTask<AnyObject>) -> Any? in
if let error = task.error {
print("Error in \(#function)\n" + error.localizedDescription)
print("Error in \(#function)\n\(error)")
return nil
}
.....
});
Here I get this error:
The operation couldn’t be completed. (com.amazonaws.AWSServiceErrorDomain error 11.)
UserInfo={HostId=ND....../FY=, Message=Access Denied, Code=AccessDenied, RequestId=EAC.....}
Having some doubts, I have also tried using .USEast1 instead of .APNortheast1, in which case the error becomes:
The operation couldn’t be completed. (com.amazonaws.AWSS3ErrorDomain error 0.)
But since my bucket is set in Asia Pacific (Tokyo) I presume the correct setting is .APNortheast1.
Can someone point out some problem in what I am doing?
Related
Is there anything else on iOS like getExternalStorageDirectory() ?
Is it getApplicationDocumentsDirectory() ?
If so, can the user access it?
The files in getApplicationDocumentsDirectory() can be shown as a list in the flutter iOS app?
use the path package, supported on all main os
https://pub.dev/packages/path
Unfortunately, you cannot access other app directories except for yours in iOS because of sandboxing. You can read it here as well:
https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
By the way, there is a way to get other directories using swift as provided in the documentation, but I did not see any solution for it using flutter.
Hope it helps you.
If I'm not mistaken, you are trying to get another application directory in iOS using flutter.
There is a way to do so.
At first, let me mention that you do not need any permission for writing & reading data in iOS. It is given by default. But, the problem is getting their path. As others already mentioned that, iOS uses sandboxing, you cannot directly get access to all files and folders excluding shared storage.
Steps you need to do for reading and writing directories of other apps.
Install file_picker package. Link: https://pub.dev/packages/file_picker
Using it, popup system directory picker:
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
PS: Users should know which folder they need to get an access.
3. When they select the folder, get the folder path and use it as you want. But there is still one thing to complete. You need to use a little bit Swift code for getting access it.
import UIKit
import Flutter
import Photos
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "example.startAccessingToSharedStorage",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
guard call.method == "startAccessingToSharedStorage" else {
result(FlutterMethodNotImplemented)
return
}
print("\(call.arguments)")
self?.startAccessingToSharedStorage(result: result, call: call)
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func startAccessingToSharedStorage(result: FlutterResult, call: FlutterMethodCall) {
let args = call.arguments as? Dictionary<String, Any>
if(args != nil){
let fileUrl = URL(fileURLWithPath: (args!["url"] as? String) ?? "")
// Get bookmark data from the provided URL
let bookmarkData = try? fileUrl.bookmarkData()
if let data = bookmarkData {
// Save data
} else {
result("Some bad thing happened")
}
// Access to an external document by the bookmark data
if let data = bookmarkData {
var stale = false
if let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &stale),
stale == false,
url.startAccessingSecurityScopedResource()
{
var error: NSError?
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { readURL in
if let data = try? Data(contentsOf: readURL) {
result("Error occured while getting access")
}
}
result("\(url.startAccessingSecurityScopedResource())\(args!["url"])")
}
}
} else {result("\(args!["url"])")}
}
}
Use method channel for using this function in flutter.
Yes, on iOS in order to get path set import:
import 'package:path_provider/path_provider.dart' as syspath;
then use:
final appDir = await syspath
.getApplicationDocumentsDirectory();
if you save the path, keep in mind that on iOS the path changes every time we run the application.
I'm using Amazon Cognito for authentication and AWS iOS SDK v. 2.6.11 in my project. My app has the following flow on the main view: Get session, then make an API call using subclass of AWSAPIGateway class.
The issue here is that after successfully authenticating with Amazon Cognito, the API call response code is 403.
After stopping the app and then running it again (now the user is already authenticated) the response status code from the API is 200.
This is the message in responseData I get from the API call with 403 response:
"Message":"User: arn:aws:sts::############:assumed-role/####_unauth_MOBILEHUB_##########/CognitoIdentityCredentials is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:############:********####:##########/Development/POST/my-api-endpoint
(identifiers replaced with # characters)
It seems that the API calls are unauthorized. Is there a way to make those API calls authorized after an successful authentication?
This is the authentication code in my initial UIViewController:
let user = pool.currentUser() ?? pool.getUser()
user.getSession("myUsername", password: "myPassword", validationData: nil).continueOnSuccessWith { sessiontask -> Any? in
// i've left error handling out of this example code
let request = AWSAPIGatewayRequest(httpMethod: "POST",
urlString: "/my-api-endpoint",
queryParameters: nil,
headerParameters: nil,
httpBody: nil)
let serviceClient = AWSAPI_MY_AUTOGENERATED_Client.default()
return serviceClient.invoke(request).continueOnSuccessWith(block: { (task) -> Any? in
if let result = task.result, result.statusCode == 200 {
// A: all good - Continue
} else {
// B: Handle error (403 etc.)
}
return nil
})
This is how my AppDelegate looks like:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let pool = AWSCognitoIdentityUserPool.default()
let credentialsProvider = AWSMobileClient.sharedInstance().getCredentialsProvider()
let configuration = AWSServiceConfiguration(
region: .EUCentral1,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
// keeping reference to the pool and the credentials provider
self.pool = pool
self.credentialsProvider = credentialsProvider
window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = MyInitialViewController()
window!.rootViewController = rootViewController
window!.makeKeyAndVisible()
return AWSMobileClient.sharedInstance().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)
}
I am trying to use AWS Cognito User Pools to add login/signup functionality for my swift iOS app. I have set up my xcworkspace with Cocoapods. In my App Delegate I have set up my Credentials Provider and Service Configuration seen below.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: cognitoIdentityPoolId)
let defaultServiceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
return true
}
but when i try to set up the user pool configuration with:
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(clientId: "###", clientSecret: "#########", poolId: "###")
i get a compilation error of "use of unresolved identifier AWSCognitoIdentityUserPoolConfiguration", which i dont understand because i have imported AWSCore and AWSCognito
any help or insight would be much appreciated thanks
import AWSCognitoIdentityProvider
Must also be added to the project and imported in your AppDelegate class in order for that method to be accessible.
I'm building an iOS (Swift) app using AWS as the backend with Developer Authenticated Identities. Everything works fine until I close the app, leave it for a while and then relaunch. In this scenario I often, but not always, receive ExpiredTokenException errors when trying to retrieve data from AWS.
Here is my code:
class DeveloperAuthenticatedIdentityProvider: AWSAbstractCognitoIdentityProvider {
var _token: String!
var _logins: [ NSObject : AnyObject ]!
override var token: String {
get {
return _token
}
}
override var logins: [ NSObject : AnyObject ]! {
get {
return _logins
}
set {
_logins = newValue
}
}
override func getIdentityId() -> AWSTask! {
if self.identityId != nil {
return AWSTask(result: self.identityId)
} else {
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if self.identityId == nil {
return self.refresh()
}
return AWSTask(result: self.identityId)
})
}
}
override func refresh() -> AWSTask! {
let apiUrl = "https://url-goes-here" // call my server to retrieve an OpenIdToken
request.GET(apiUrl, parameters: nil, progress: nil,
success: {
(task: NSURLSessionDataTask, response: AnyObject?) -> Void in
let tmp = NSMutableDictionary()
tmp.setObject("temp", forKey: "ExampleApp")
self.logins = tmp as [ NSObject : AnyObject ]
let jsonDictionary = response as! NSDictionary
self.identityId = jsonDictionary["identityId"] as! String
self._token = jsonDictionary["token"] as! String
awstask.setResult(response)
},
failure: {
(task: NSURLSessionDataTask?, error: NSError) -> Void in
awstask.setError(error)
}
)
return awstask.task
}
}
And in the AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let identityProvider = DeveloperAuthenticatedIdentityProvider()
// set default service configuration
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: cognitoRegion, identityProvider: identityProvider, unauthRoleArn: unauthRole, authRoleArn: authRole)
let configuration = AWSServiceConfiguration(region: defaultServiceRegion, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
// set service configuration for S3 (my bucket is located in a different region to my Cognito and Lambda service)
let credentialsProviderForS3 = AWSCognitoCredentialsProvider(regionType: cognitoRegion, identityProvider: identityProvider, unauthRoleArn: unauthRole, authRoleArn: unauthRole)
let awsConfigurationForS3 = AWSServiceConfiguration(region: s3ServiceRegion, credentialsProvider: credentialsProviderForS3)
AWSS3TransferUtility.registerS3TransferUtilityWithConfiguration(awsConfigurationForS3, forKey: "S3")
return true
}
This post suggests that the Cognito token has expired and it is up to the developer to manually refresh. This seems overly complex as it would require setting a timer to refresh regularly, handling app closures and relaunches and handling AWS requests that occur while the refresh is taking place. Is there a simpler way? For example, is it possible to have the AWS SDK automatically call refresh whenever it attempts to query the server using an expired token?
Any help would be appreciated. I'm using version 2.3.5 of the AWS SDK for iOS.
The AWS Mobile SDK for iOS 2.4.x has a new protocol called AWSIdentityProviderManager. It has the following method:
/**
* Each entry in logins represents a single login with an identity provider.
* The key is the domain of the login provider (e.g. 'graph.facebook.com') and the value is the
* OAuth/OpenId Connect token that results from an authentication with that login provider.
*/
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins;
The responsibility of an object conforming to this protocol is to return a valid logins dictionary whenever it is requested. Because this method is asynchronous, you can make networking calls in it if the cached token is expired. The implementation is up to you, but in many cases, AWSIdentityProviderManager manages multiple AWSIdentityProviders, aggregates them and return the logins dictionary.
Unfortunately developers refreshing the token is the only way.
I agree that it would be simpler for app developers if AWS SDK handled this but the way CrdentialsProvider is designed is supposed to be generic for all providers. For example, if someone wants to use Facebook as provider then AWS SDK will not be able to handle the refresh on its own and developer will have t handle that in his app. Keeping the refresh flow out of the SDK gives us the capability to keep the CredentialsProvider generic.
I am working on a project involving an iOS app communicating with another device by way of syncing data through Dropbox.
It works perfectly fine when I run the software on the iPhone Simulator (it syncs, uploads, downloads without issues), but when I load it onto my actual device, I get load/save errors.
The app on both the simulator and iPhone was successfully linked to my Dropbox account.
Some errors when I try do load requests:
2015-05-18 23:27:19.385 [2218:923269] [ERROR] DBRequest#connectionDidFinishLoading: error moving temp file to desired location: The operation couldn’t be completed. (Cocoa error 516.)
2015-05-18 23:27:19.387 [2218:923269] [WARNING] DropboxSDK: error making request to /1/files/dropbox/Projekt 2 (1)/Program/Units.txt - (516) Error Domain=NSCocoaErrorDomain Code=516 "The operation couldn’t be completed. (Cocoa error 516.)" UserInfo=0x174077700 {path=/Projekt 2 (1)/Program/Units.txt, destinationPath=/...}
Samples of the dropbox related code in my app:
In AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let session = DBSession(appKey: "myAppKey", appSecret: "myAppSecret", root: kDBRootDropbox)
DBSession.setSharedSession(session)
...
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if DBSession.sharedSession().handleOpenURL(url) {
if DBSession.sharedSession().isLinked() {
// Linking was successfull.
}
return true
}
return false
}
In ViewControllerCausingErrors.swift:
class ViewControllerCausingErrors: DBRestClientDelegate {
var dbClient = DBRestClient()
override func viewDidLoad() {
super.viewDidLoad()
self.dbClient = DBRestClient(session: DBSession.sharedSession())
self.dbClient.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated: animated)
if !DBSession.sharedSession().isLinked() {
DBSession.sharedSession().linkFromController(self)
}
}
}
Chunk of code i use to download a file, elsewhere in the VC
if let localPath = NSBundle.mainBundle().pathForResource("Units", ofType: "txt") {
// Download file from Dropbox to local path.
let dropboxPath = Constants.Dropbox.Download.UnitFilePath
self.dbClient.loadFile(dropboxPath, intoPath: localPath)
}
Any help is greatly appreciated.
According to the iOS documentation, error code 516 is:
NSFileWriteFileExistsError = 516,
It sounds like there's a file at the supplied localPath on the device (called destinationPath in the error), but not on the simulator, causing loadFile to not be able to write the file from the download.
I think problem is in the NSBundle.mainBundle().pathForResource("Units", ofType: "txt"). NSBundle is used in simulator but not in actual device. You just place Units.txt in your loadFile function
self.dbClient.loadFile(dropboxPath, intoPath: "Units.txt")