You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在同一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)里。

核心代码示例:

  1. 文件上传方法(用户选择文件后调用):
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();
    });
}
  1. 元数据存储方法:
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规则示例:
    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    Firestore规则示例:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:02:15