存储

上传示例、Supabase Storage 存储桶和媒体选择流程。

上传示例是一个 Example 功能:它展示完整的 Android 媒体选择器 -> Supabase Storage 上传流程,但刻意保持简单,方便你替换成自己的产品文件模型。

提供什么

  • 通过 ActivityResultContracts.PickMultipleVisualMedia 选择图片/视频。
  • 名为 sample-uploads 的私有 Supabase Storage bucket。
  • <user-id>/ 为前缀的用户隔离对象路径。
  • 使用 uploadAsFlow 顺序上传并报告每个任务的进度。
  • 用内存状态管理 pending、uploading、complete、failed 队列。

Kotlin 模板没有使用 Expo 示例里的 sample_uploads 元数据表。UI 只在内存里跟踪 当前上传队列。

关键文件

上传流程

选择图片或视频

UploadScreen 创建 launcher:

ActivityResultContracts.PickMultipleVisualMedia(MAX_PICK_COUNT)

启动时使用 PickVisualMedia.ImageAndVideo,所以图片和视频都能选择。页面通过 ContentResolver 读取显示名称、MIME type 和大小。

要求用户已登录

UploadViewModel 读取 sessionController.currentUser.value?.id。如果没有用户, 会显示 "Sign in to upload files.",并且不会访问 Storage。

校验大小和内容

每个文件上限是 25 MB。空文件会在上传前失败。

流式报告上传进度

UploadsRepository.upload() 写入 sample-uploads bucket,并返回 Flow<UploadStatus>。ViewModel 把 UploadStatus.Progress 映射成行进度,把 UploadStatus.Success 映射成包含最终 storage path 的完成行。

对象路径约定

UploadsRepository 写入的路径形如:

<user-id>/<epoch-millis>-<sanitized-file-name>

文件名清理器只保留字母、数字、._-,其他连续字符会变成 -,再去掉首尾 短横线;如果最终为空,则使用 upload

示例:

11111111-1111-1111-1111-111111111111/1782940800000-vacation.mov

下面的 Storage 策略依赖第一个路径段就是 Supabase user id。

创建 bucket 和策略

创建 Supabase 项目后运行:

insert into storage.buckets (id, name, public, file_size_limit)
values ('sample-uploads', 'sample-uploads', false, 26214400)
on conflict (id) do update
  set public = excluded.public,
      file_size_limit = excluded.file_size_limit;

do $$
begin
  if not exists (
    select 1 from pg_policies
    where schemaname = 'storage'
      and tablename = 'objects'
      and policyname = 'Users can read own sample upload objects'
  ) then
    execute $policy$
      create policy "Users can read own sample upload objects"
        on storage.objects for select
        to authenticated
        using (
          bucket_id = 'sample-uploads'
          and (storage.foldername(name))[1] = auth.uid()::text
        )
    $policy$;
  end if;

  if not exists (
    select 1 from pg_policies
    where schemaname = 'storage'
      and tablename = 'objects'
      and policyname = 'Users can upload own sample upload objects'
  ) then
    execute $policy$
      create policy "Users can upload own sample upload objects"
        on storage.objects for insert
        to authenticated
        with check (
          bucket_id = 'sample-uploads'
          and (storage.foldername(name))[1] = auth.uid()::text
        )
    $policy$;
  end if;

  if not exists (
    select 1 from pg_policies
    where schemaname = 'storage'
      and tablename = 'objects'
      and policyname = 'Users can update own sample upload objects'
  ) then
    execute $policy$
      create policy "Users can update own sample upload objects"
        on storage.objects for update
        to authenticated
        using (
          bucket_id = 'sample-uploads'
          and (storage.foldername(name))[1] = auth.uid()::text
        )
        with check (
          bucket_id = 'sample-uploads'
          and (storage.foldername(name))[1] = auth.uid()::text
        )
    $policy$;
  end if;

  if not exists (
    select 1 from pg_policies
    where schemaname = 'storage'
      and tablename = 'objects'
      and policyname = 'Users can delete own sample upload objects'
  ) then
    execute $policy$
      create policy "Users can delete own sample upload objects"
        on storage.objects for delete
        to authenticated
        using (
          bucket_id = 'sample-uploads'
          and (storage.foldername(name))[1] = auth.uid()::text
        )
    $policy$;
  end if;
end $$;

bucket 限制与应用内 25 MB 校验保持一致。如果提高其中一个,也要同步提高另一个。

删除账号注意点

当前 Kotlin 删除账号实现不会移除 sample-uploads 对象。它只删除 todos、清空 profile 字段并本地登出;auth user 和 Storage 文件仍会保留。

推荐的模板修复与身份认证中一致:添加 service-role delete-account Edge Function,在删除 Supabase Auth 用户之前先移除 sample-uploads/<user-id>/ 下的对象。

显示与定制

上传示例会报告最终 storage path,但不会渲染远程图库。如果你的应用需要预览,可以 使用 Coil 3(UI kit 中的 AppAsyncImage 已经包装了 Coil),再决定 bucket 是使用 私有 signed URL,还是使用公开 CDN-backed object URL。

把示例改成产品功能时,通常会做这些事:

  • 重命名 UploadsRepository.BUCKET_ID
  • 如果产品需要相册、团队或对象 id,调整对象路径约定。
  • 如果文件需要标题、更多所有权、审核状态或历史列表,添加元数据表。
  • 让 Storage 策略始终与路径形状保持一致。

验证

  • sample-uploads 存在且是 private。
  • 已登录用户可以上传 25 MB 以下的图片。
  • 返回的 storage path 以该用户 UUID 开头。
  • 另一个已登录用户不能读取或覆盖该对象。
  • 未登录用户不能上传。

相关页面

On this page