如何在同一Activity中实现Firebase文件上传及按类别分RecyclerView加载数据
嘿,这个需求我刚好做过类似的,给你一步步拆解实现思路和关键代码,应该能帮到你:
一、前期准备:配置Firebase服务
- 先在Firebase控制台创建项目,启用Cloud Storage和Cloud Firestore两个服务
- 在Android项目的
app/build.gradle里添加依赖:// Firebase Storage implementation 'com.google.firebase:firebase-storage:20.3.0' // Firebase Firestore implementation 'com.google.firebase:firebase-firestore:24.10.0' - 初始化Firebase(一般项目里会自动生成初始化代码,要是没有的话,在自定义Application类里加
FirebaseApp.initializeApp(this);)
二、文件上传 + 元数据存储到Firestore
上传逻辑分两步:先把文件传到Firebase Storage拿到下载URL,再把文件名、URL、类别一起存到Firestore的集合(比如命名为file_metadata)里。
核心代码示例:
- 文件上传方法(用户选择文件后调用):
private void uploadFileToFirebase(File file, String category) { // 生成唯一文件名,避免重复覆盖 String fileName = UUID.randomUUID().toString() + "_" + file.getName(); StorageReference storageRef = FirebaseStorage.getInstance().getReference("files/" + category + "/" + fileName); // 执行上传 UploadTask uploadTask = storageRef.putFile(Uri.fromFile(file)); uploadTask.addOnSuccessListener(taskSnapshot -> { // 获取文件的下载URL storageRef.getDownloadUrl().addOnSuccessListener(uri -> { String fileUrl = uri.toString(); // 将元数据存入Firestore saveFileMetadataToFirestore(fileName, fileUrl, category); }).addOnFailureListener(e -> { Toast.makeText(this, "获取文件URL失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); }); }).addOnFailureListener(e -> { Toast.makeText(this, "文件上传失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); }); }
- 元数据存储方法:
private void saveFileMetadataToFirestore(String fileName, String fileUrl, String category) { FirebaseFirestore db = FirebaseFirestore.getInstance(); Map<String, Object> fileData = new HashMap<>(); fileData.put("fileName", fileName); fileData.put("fileUrl", fileUrl); fileData.put("category", category); fileData.put("uploadTime", FieldValue.serverTimestamp()); // 可选:记录上传时间 db.collection("file_metadata") .add(fileData) .addOnSuccessListener(documentReference -> { Toast.makeText(this, "文件上传并记录成功", Toast.LENGTH_SHORT).show(); }) .addOnFailureListener(e -> { Toast.makeText(this, "记录元数据失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); }); }
三、在同一Activity中按类别加载数据到不同RecyclerView
假设你需要区分image和document两个类别,用两个RecyclerView分别展示。
步骤1:布局文件(activity_main.xml)
在布局里放置两个RecyclerView,加上标题区分:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="图片文件" android:textSize="18sp" android:textStyle="bold"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_images" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginTop="8dp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="文档文件" android:textSize="18sp" android:textStyle="bold" android:layout_marginTop="16dp"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_documents" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginTop="8dp"/> </LinearLayout>
步骤2:创建通用的Model和Adapter
因为两个RecyclerView的数据结构一致,可以复用Model和Adapter:
// FileModel.java public class FileModel { private String fileName; private String fileUrl; private String category; // Firestore序列化需要空构造函数 public FileModel() {} public FileModel(String fileName, String fileUrl, String category) { this.fileName = fileName; this.fileUrl = fileUrl; this.category = category; } // Getters & Setters public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getFileUrl() { return fileUrl; } public void setFileUrl(String fileUrl) { this.fileUrl = fileUrl; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }
// FileAdapter.java public class FileAdapter extends RecyclerView.Adapter<FileAdapter.FileViewHolder> { private List<FileModel> fileList; private OnFileClickListener listener; public interface OnFileClickListener { void onFileClick(FileModel file); } public FileAdapter(List<FileModel> fileList, OnFileClickListener listener) { this.fileList = fileList; this.listener = listener; } @NonNull @Override public FileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_file, parent, false); return new FileViewHolder(view); } @Override public void onBindViewHolder(@NonNull FileViewHolder holder, int position) { FileModel file = fileList.get(position); holder.tvFileName.setText(file.getFileName()); holder.itemView.setOnClickListener(v -> listener.onFileClick(file)); } @Override public int getItemCount() { return fileList.size(); } // 更新列表数据 public void updateList(List<FileModel> newList) { fileList.clear(); fileList.addAll(newList); notifyDataSetChanged(); } static class FileViewHolder extends RecyclerView.ViewHolder { TextView tvFileName; public FileViewHolder(@NonNull View itemView) { super(itemView); tvFileName = itemView.findViewById(R.id.tv_file_name); } } }
对应的item布局(item_file.xml):
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_file_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="12dp" android:textSize="16sp"/>
步骤3:在Activity中加载数据并绑定RecyclerView
在Activity的onCreate方法里初始化组件,分别查询对应类别的数据:
public class MainActivity extends AppCompatActivity implements FileAdapter.OnFileClickListener { private RecyclerView rvImages, rvDocuments; private FileAdapter imageAdapter, documentAdapter; private List<FileModel> imageList, documentList; private FirebaseFirestore db; // 存储Firestore的监听实例,用于销毁时移除 private ListenerRegistration imageListener, documentListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化RecyclerView rvImages = findViewById(R.id.rv_images); rvDocuments = findViewById(R.id.rv_documents); rvImages.setLayoutManager(new LinearLayoutManager(this)); rvDocuments.setLayoutManager(new LinearLayoutManager(this)); // 初始化数据列表和Adapter imageList = new ArrayList<>(); documentList = new ArrayList<>(); imageAdapter = new FileAdapter(imageList, this); documentAdapter = new FileAdapter(documentList, this); rvImages.setAdapter(imageAdapter); rvDocuments.setAdapter(documentAdapter); // 初始化Firestore db = FirebaseFirestore.getInstance(); // 加载不同类别的数据 loadFilesByCategory("image"); loadFilesByCategory("document"); } private void loadFilesByCategory(String category) { Query query = db.collection("file_metadata") .whereEqualTo("category", category) .orderBy("uploadTime", Query.Direction.DESCENDING); // 按上传时间倒序展示 if ("image".equals(category)) { imageListener = query.addSnapshotListener((value, error) -> { if (error != null) { Log.e("Firestore", "加载图片数据失败:" + error.getMessage()); return; } if (value != null && !value.isEmpty()) { imageList.clear(); for (QueryDocumentSnapshot doc : value) { FileModel file = doc.toObject(FileModel.class); imageList.add(file); } imageAdapter.updateList(imageList); } }); } else if ("document".equals(category)) { documentListener = query.addSnapshotListener((value, error) -> { if (error != null) { Log.e("Firestore", "加载文档数据失败:" + error.getMessage()); return; } if (value != null && !value.isEmpty()) { documentList.clear(); for (QueryDocumentSnapshot doc : value) { FileModel file = doc.toObject(FileModel.class); documentList.add(file); } documentAdapter.updateList(documentList); } }); } } @Override public void onFileClick(FileModel file) { // 处理文件点击事件,比如打开文件或跳转下载 Toast.makeText(this, "点击了文件:" + file.getFileName(), Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); // 移除监听,避免内存泄漏 if (imageListener != null) imageListener.remove(); if (documentListener != null) documentListener.remove(); } }
四、几个关键注意点
- 权限配置:在
AndroidManifest.xml中添加必要权限:<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- Android 13+ 需要单独申请媒体权限,按需添加 --> - Firebase规则设置:开发阶段可以暂时开放规则方便测试,上线后一定要改成安全规则(比如只允许登录用户读写):
Storage规则示例:
Firestore规则示例:rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth != null; } } }rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /file_metadata/{doc} { allow read, write: if request.auth != null; } } } - 上传进度提示:如果需要显示上传进度,给
UploadTask添加进度监听:uploadTask.addOnProgressListener(snapshot -> { double progress = (100.0 * snapshot.getBytesTransferred()) / snapshot.getTotalByteCount(); // 更新ProgressBar进度 progressBar.setProgress((int) progress); }); - 内存管理:务必在Activity销毁时移除Firestore的监听实例,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Pavan Sharma
相关产品推荐
相关产品推荐

