Repositório GitHubjvoltci/saf

segurança

Plugin Flutter que utiliza a API do Storage Access Framework (SAF) para obter acesso e realizar operações em arquivos e pastas.

Capa do projeto segurança
Estrelas
25
Forks
38
Último push (UTC)
25 de ago. de 2026
Status do projeto
Ativo

README original

Texto original em inglês. Visite o GitHub para a versão atual.

saf — one class for the Android Storage Access Framework

Saf CI pipeline status

Saf

Scoped storage broke File('/storage/emulated/0/…'). saf is the fix.

One package for the Android Storage Access Framework: pickers, persisted permissions, file management with recursive walk, streamed read/write with progress, thumbnails, raw file descriptors, and local-file bridging — all in a single Saf class. Ask the user once, keep the grant forever, and work with their files in a handful of lines instead of hundreds of lines of platform code.

flutter pub add saf

Highlights

  • One class, no ceremony — pickers, permissions, file management, and I/O all on Saf; no isDir parameters anywhere.
  • Typed errors — SafPermissionException, SafNotFoundException, SafAlreadyExistsException, SafIoException.
  • Recursive walk() plus recursive copyTo / moveTo with progress callbacks.
  • Streaming I/O — backpressured readFileStream and one-call writeFileStream for large files.
  • File descriptors — openFileDescriptor / withFileDescriptor hand native or path-based APIs a live /proc/self/fd/<fd>.
  • Thumbnails — thumbnail() returns provider-generated JPEG bytes for images and videos.
  • Persisted permissions — grant once, reuse across restarts; list them with persistedPermissions().
  • Hidden folders — read dotfile folders (e.g. WhatsApp .Statuses) and pull them into your app dir with copyDirToLocal.
  • Broad support — Dart ≥ 3.0, Flutter ≥ 3.10, Android minSdk 21.

Quick start

import 'dart:convert';
import 'dart:typed_data';
import 'package:saf/saf.dart';

final saf = Saf();

// 1. Ask once — the grant persists across restarts.
final dir = await saf.pickDirectory();
if (dir == null) return; // user cancelled

// Later launches: reuse the grant instead of prompting again.
final grants = await saf.persistedPermissions();

// 2. Manage files.
final files = await saf.list(dir.uri);
final report = await saf.mkdirp(dir.uri, ['reports', '2026']);
await for (final entry in saf.walk(dir.uri)) {
  print(entry.relativePath);
}

// 3. Read and write.
final doc = await saf.writeFileBytes(
    report.uri, 'summary.txt', 'text/plain', utf8.encode('hi') as Uint8List);
final bytes = await saf.readFileBytes(doc.uri);
final stream = await saf.readFileStream(doc.uri); // large files

// 4. Bridge to real file paths when another API needs one.
await saf.copyToLocalFile(doc.uri, '${cacheDir.path}/summary.txt',
    onProgress: (p) => print('${p.bytesDone}/${p.totalBytes}'));

// 5. Thumbnails for a gallery grid — provider-generated, no full decode.
final jpeg = await saf.thumbnail(doc.uri, 256, 256, 80); // Uint8List? for Image.memory

// 6. Hand a SAF file to anything that wants a real path or fd —
//    video players, PDF renderers, sqlite — no copy, auto-closed.
final title = await saf.withFileDescriptor(doc.uri, 'r', (fd) async {
  return someNativeLib.readMetadata(fd.path); // /proc/self/fd/<fd>
});

Errors are typed — catch what you care about:

try {
  await saf.delete(uri);
} on SafPermissionException {
  // re-pick the directory
} on SafNotFoundException {
  // already gone
}

Migrating

From saf 1.x

The old path-based class still works as LegacySaf (deprecated, removed in 3.0.0): rename Saf( → LegacySaf( and migrate at your own pace. The new API is URI-based — start from pickDirectory() and store URIs, not paths.

Scope

saf keeps a focused, purposeful API. Raw file descriptors and provider thumbnails now ship (see openFileDescriptor / withFileDescriptor and thumbnail). Intentionally out of scope for now:

  • Media picking — use image_picker / photo_manager, which specialize in it.

Architecture

saf is a single package with a mockable platform-interface layer, a thin Dart facade, and one coroutine-based Kotlin handler on a dedicated channel — the legacy 1.x channels are left untouched.

Flow: your app → Saf (facade) → SafPlatform → method channel → SafV2Api (Kotlin, coroutines) → DocumentsContract → Android SAF. All I/O runs off the main thread.

See the architecture page for layer diagrams and a grant-then-read sequence.

Try it in 30 seconds

The bundled example is a mini file manager: pick a folder, browse it with live image thumbnails, tap a file for its details and file-descriptor path, write a file back. cd example && flutter run.

Quality

  • Tested — full unit-test suite on the Dart layer; exercised end-to-end on physical devices.
  • Hardened — the file layer is written defensively: an aborted write never deletes a pre-existing file, recursive copy refuses to recurse into itself, and every picker/stream failure path cleans up after itself.
  • Typed & mockable — sealed exceptions and a platform-interface layer you can fake in your own tests.

Documentation


Built & maintained by jvoltci  ·  Docs  ·  Issues  ·  pub.dev  ·  MIT License

⭐ If saf saves you time, star the repo — it helps other Flutter devs find it.

Jai Prakashsingh GitHub avatar
Autor no GitHub

Jai Prakashsingh ↗

LLM Inference & AI Systems • Serving Engines • KV-Cache • GPU Kernels • C++ • CUDA • Triton

Varanasi
LinguagensDartKotlin

Categorias

Dependências usadas

Lista de dependências 4 itens

  • flutter{"sdk":"flutter"}
  • plugin_platform_interface^2.1.8
  • flutter_testDesenvolvimento{"sdk":"flutter"}
  • flutter_lintsDesenvolvimento^5.0.0

Como escolher um repositório para seu projeto?

Filtre pelo caso de uso, compare a atividade e confira o projeto original antes de adotá-lo.

  1. Busque por nome, autor ou descrição e filtre por tema.
  2. Compare estrelas, forks e envios recentes. Estrelas indicam interesse, mas não garantem qualidade.
  3. Confira o README, a licença e as issues no GitHub antes de usar o projeto.

Fontes e manutenção

“A repository is the most basic element of GitHub.”

GitHub Docs — About repositories ↗