# T16 — Export UI **Phase** 6 · **Depends on** T15 · **Status** Done ## Goal Get an exported ride off the phone — share sheet or save to a chosen location. Done when a GPX can be shared to another app or written to storage and opens correctly there. ## Context T15 produces strings; this task moves them somewhere useful. Two paths cover the real uses: - **Share** — send to Strava, Drive, email, Slack. The common case. - **Save** — write to Downloads or a chosen folder via SAF. For getting it onto a computer. `FileProvider` is required — since Android 7 a raw `file://` URI in an Intent throws `FileUriExposedException`. The app currently has no `` declared and no `file_paths.xml`; both are new. ## Design ### FileProvider ```xml ``` Files are written to `cacheDir/exports/`, exposed as a `cache-path`. Cache is right: these are transient handoffs, and the system can reclaim them. Clear stale exports on app start so the directory does not grow unbounded. `FLAG_GRANT_READ_URI_PERMISSION` on the share Intent, or the receiving app gets a `SecurityException`. ### Save via SAF `ActivityResultContracts.CreateDocument("application/gpx+xml")` returns a user-chosen URI; write through `contentResolver.openOutputStream`. No storage permission needed, which is the point of SAF. ### Filename `rippr-2026-08-10-1432.gpx` — sortable, unambiguous, no spaces. Derived from `startedAt` in the device timezone (the filename is for a human, unlike the timestamps *inside* the file, which are UTC). ### UI Export action on trip detail, offering format (GPX / GeoJSON) then destination (Share / Save). Two small choices rather than four buttons. Generation happens **off the main thread** with a progress indicator — a large ride is a few MB of string building. ## Implementation 1. Add the `` to `AndroidManifest.xml` and create `res/xml/file_paths.xml`. 2. `export/ExportManager.kt`: generate on IO, write to `cacheDir/exports/`, return a `FileProvider` URI. 3. Share via `Intent.ACTION_SEND` with the correct MIME type and read permission flag. 4. SAF save via `CreateDocument`. 5. Export UI on trip detail with format and destination choice. 6. Clear `cacheDir/exports/` on app start. 7. MIME types: `application/gpx+xml`, `application/geo+json`. ## Acceptance criteria - [x] Share opens the system sheet with a valid attachment - [x] The shared file opens correctly in a receiving app - [ ] SAF save writes to the chosen location — **not implemented**; share only - [x] No `FileUriExposedException` - [x] No storage permission added to the manifest - [x] Generation is off the main thread with a progress indicator - [x] Stale exports cleared on start > **Scope reduced.** Only the share sheet shipped. Saving via SAF was dropped: the > share sheet already reaches Drive, Files, email and Strava, which covers getting a > ride off the phone. Add SAF if a real need appears. ## Tests Instrumented: `ExportManager` produces a readable `FileProvider` URI whose content matches `GpxWriter.write` exactly; cache clearing works. Manual on emulator: share to a file manager, pull the file with `adb pull`, and diff it against locally-generated output. ## Risks / gotchas - **`FileUriExposedException`** is the failure mode if `FileProvider` is skipped — it throws at share time, not build time. - **Missing `FLAG_GRANT_READ_URI_PERMISSION`** produces a `SecurityException` in the *receiving* app, which reads as that app being broken. - **Authority must be unique** — `${applicationId}.fileprovider` guarantees it. - **Do not write to external storage directly.** SAF exists to avoid that permission. ## Out of scope Cloud upload, auto-export on trip completion, import.