# Storage — Mobile

## Prerequisites

Initialize Amplify with Auth and Storage plugins before using this feature:

**Flutter** — `lib/main.dart`:

```dart
await Amplify.addPlugins([AmplifyAuthCognito(), AmplifyStorageS3()]);
await Amplify.configure(amplifyConfig);
```

> Generate dart outputs: `npx ampx sandbox --outputs-format dart --outputs-out-dir lib`

**Swift (Apple platforms):**

```swift
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.add(plugin: AWSS3StoragePlugin())
try Amplify.configure(with: .amplifyOutputs)
```

> Drag `amplify_outputs.json` into the Xcode project navigator so it is included in the app bundle.

**Android:**

```kotlin
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.addPlugin(AWSS3StoragePlugin())
Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), applicationContext)
```

> Place `amplify_outputs.json` in `app/src/main/res/raw/`. Enable core library desugaring for API level < 26.
>
> **Backend required:** Storage must be defined in `amplify/storage/resource.ts`
> using `defineStorage` — see [storage-backend.md](storage-backend.md).

## Flutter

Imports: `amplify_flutter` + `amplify_storage_s3`. All paths wrapped with `StoragePath.fromString()`.

| Operation     | Call                                                                                                                    |
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Upload file   | `Amplify.Storage.uploadFile(localFile: AWSFile.fromPath(path), path: const StoragePath.fromString('public/photo.jpg'))` |
| Download file | `Amplify.Storage.downloadFile(path: const StoragePath.fromString('public/photo.jpg'), localFile: localFile)`            |
| List          | `Amplify.Storage.list(path: const StoragePath.fromString('public/'))` → `.result.items`                                 |
| Presigned URL | `Amplify.Storage.getUrl(path: const StoragePath.fromString('public/file.jpg'))`                                         |
| Remove        | `Amplify.Storage.remove(path: const StoragePath.fromString('public/file.jpg'))`                                         |

> **Security:** Amplify Gen2 enables S3 server-side encryption (SSE-S3) by default. All transfers use HTTPS (TLS in transit). For sensitive data, configure SSE-KMS with a customer-managed key via CDK overrides.

Upload progress — use the `onProgress` callback parameter:

```dart
final op = Amplify.Storage.uploadFile(
  localFile: AWSFile.fromPath('/path/to/file'),
  path: const StoragePath.fromString('public/photos/photo.jpg'),
  onProgress: (p) => print('fraction: ${p.fractionCompleted}'),
);
final result = await op.result;
```

Use `const` with `StoragePath.fromString()` for compile-time constant paths.

## Swift (Apple platforms)

> Supported: iOS 13+, macOS 12+, tvOS 13+, watchOS 9+, visionOS 1+ (preview).

Uses `Amplify.Storage` with async/await. Import: `Amplify`.

| Operation     | Call                                                                                                        |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| Upload data   | `Amplify.Storage.uploadData(path: .fromString("public/file.txt"), data: data)` → `try await task.value`     |
| Upload file   | `Amplify.Storage.uploadFile(path: .fromString("public/file.txt"), local: fileUrl)` → `try await task.value` |
| Download data | `Amplify.Storage.downloadData(path: .fromString("public/file.txt"))` → `.value` returns `Data`              |
| Download file | `Amplify.Storage.downloadFile(path: .fromString("public/path"), local: fileUrl)` → `try await task.value`   |
| List          | `try await Amplify.Storage.list(path: .fromString("public/"))` → `.items`                                   |
| Presigned URL | `try await Amplify.Storage.getURL(path: .fromString("public/file.jpg"))`                                    |
| Remove        | `try await Amplify.Storage.remove(path: .fromString("public/file.jpg"))`                                    |

**Download with progress tracking:**

```swift
let downloadTask = Amplify.Storage.downloadData(
    path: .fromString("public/example.jpg")
)
Task {
    for await progress in await downloadTask.progress {
        print("Progress: \(progress.fractionCompleted)")
    }
}
let data = try await downloadTask.value
```

**Upload with progress tracking:**

```swift
let uploadTask = Amplify.Storage.uploadData(
    path: .fromString("public/photo.jpg"),
    data: imageData
)
Task {
    for await progress in await uploadTask.progress {
        print("Progress: \(progress)")
    }
}
let result = try await uploadTask.value
```

Use SwiftUI's `PhotosPicker` (from `import PhotosUI`) to obtain image data,
then pass to `uploadData`.

## Android (Kotlin)

Android supports both callback-based and coroutine-based APIs.
Import: `com.amplifyframework.core.Amplify`, `com.amplifyframework.storage.StoragePath`.

**Coroutine example (recommended):**

```kotlin
private suspend fun uploadFile() {
    val exampleFile = File(applicationContext.filesDir, "example")
    exampleFile.writeText("Example file contents")
    val upload = Amplify.Storage.uploadFile(
        StoragePath.fromString("public/example"), exampleFile
    )
    try {
        val result = upload.result()
        Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}")
    } catch (error: StorageException) {
        Log.e("MyAmplifyApp", "Upload failed", error)
    }
}
```

```kotlin
private suspend fun downloadFile() {
    val download = Amplify.Storage.downloadFile(
        StoragePath.fromString("public/example"), localFile
    )
    try {
        val result = download.result()
        Log.i("MyAmplifyApp", "Successfully downloaded: ${result.file.name}")
    } catch (error: StorageException) {
        Log.e("MyAmplifyApp", "Download failed", error)
    }
}
```

| Operation (coroutine) | Call                                                                                                |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| Upload file           | `Amplify.Storage.uploadFile(StoragePath.fromString("public/photo.jpg"), file)` → `.result()`        |
| Upload stream         | `Amplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), stream)` → `.result()` |
| Download file         | `Amplify.Storage.downloadFile(StoragePath.fromString("public/photo.jpg"), localFile)` → `.result()` |
| List                  | `Amplify.Storage.list(StoragePath.fromString("public/"))` → `.items`                                |
| Presigned URL         | `Amplify.Storage.getUrl(StoragePath.fromString("public/file.jpg"))` → `.url`                        |
| Remove                | `Amplify.Storage.remove(StoragePath.fromString("public/file.jpg"))`                                 |

**Callback alternative:** all operations also accept `onSuccess`/`onError` lambdas — e.g.
`Amplify.Storage.uploadFile(StoragePath.fromString("public/photo.jpg"), file, { result -> ... }, { error -> ... })`.

## Permissions

For authenticated user paths, use `protected/{entity_id}/` or `private/{entity_id}/` — the `{entity_id}` resolves to the user's Cognito identity ID at runtime.

- **Android:** Verify `INTERNET` permission is declared in `AndroidManifest.xml` (usually present by default). If the app accesses the camera, add `CAMERA`; for gallery access, add `READ_MEDIA_IMAGES` (API 33+) or `READ_EXTERNAL_STORAGE` (older).
- **Apple (iOS/macOS):** No special permissions for S3 storage operations. If the app accesses the camera, add `NSCameraUsageDescription` in `Info.plist`. If the app accesses the photo library, add `NSPhotoLibraryUsageDescription`.
- **Flutter:** Follows Android/iOS rules above — add permissions in `AndroidManifest.xml` and `Info.plist` respectively.

## Pitfalls

- **Swift SDK uses `getURL` (capital URL), not `getUrl`:** Using the
  wrong casing (lowercase `l`) causes compile errors. JS/web uses
  `getUrl` (lowercase), but Swift uses `getURL`.
- **Wrong file wrapper per platform:** Flutter requires
  `AWSFile.fromPath()`, Swift uses `Data` (for `uploadData`) or a file
  URL (for `uploadFile`), Android uses `File`. Using the wrong type
  causes compile errors — check the platform's expected input.
- **Missing `StoragePath.fromString()`:** Flutter and Android require
  `StoragePath.fromString('path')` to wrap path strings. Passing a raw
  string literal does not compile.
- **Large file uploads on mobile:** For files over 5 MB, the SDK
  automatically uses multipart upload. You **SHOULD** implement
  progress tracking (`onProgress` in Flutter, `for await progress in ...`
  in Swift, `transferObserver` or progress callback in Android) to show
  upload progress to the user.

## Links

- [Storage Overview (Android)](https://docs.amplify.aws/android/build-a-backend/storage/)
- [Set Up Storage (Android)](https://docs.amplify.aws/android/build-a-backend/storage/set-up-storage/)
- [Upload Files (Android)](https://docs.amplify.aws/android/frontend/storage/upload-files/)
- [Download Files (Android)](https://docs.amplify.aws/android/frontend/storage/download-files/)
- [Storage Overview (Swift)](https://docs.amplify.aws/swift/build-a-backend/storage/)
- [Set Up Storage (Swift)](https://docs.amplify.aws/swift/build-a-backend/storage/set-up-storage/)
- [Upload Files (Swift)](https://docs.amplify.aws/swift/frontend/storage/upload-files/)
- [Download Files (Swift)](https://docs.amplify.aws/swift/frontend/storage/download-files/)
- [Storage Overview (Flutter)](https://docs.amplify.aws/flutter/build-a-backend/storage/)
- [Set Up Storage (Flutter)](https://docs.amplify.aws/flutter/build-a-backend/storage/set-up-storage/)
- [Upload Files (Flutter)](https://docs.amplify.aws/flutter/frontend/storage/upload-files/)
- [Download Files (Flutter)](https://docs.amplify.aws/flutter/frontend/storage/download-files/)
