存储
上传示例、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 开头。
- 另一个已登录用户不能读取或覆盖该对象。
- 未登录用户不能上传。
