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

Flutter上传页面发布时Firebase无认证令牌报错求助

问题描述

开发Flutter上传页面时,已实现相机/图库选图功能,但点击发布时控制台出现以下警告:

E/StorageUtil(13988): error getting token java.util.concurrent.ExecutionException: com.google.firebase.internal.api.FirebaseNoSignedInUserException: Please sign in before trying to get a token.
W/NetworkRequest(13988): no auth token for request
W/NetworkRequest(13988): No App Check token for request.

上传页面代码如下:

import 'dart:io';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:image/image.dart' as im;
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:social_appify/pages/home.dart';
import 'package:social_appify/widgets/progress.dart';
import 'package:uuid/uuid.dart';

import '../model/user.dart';

class UploadScreen extends StatefulWidget {
  final User? currentUser;

  const UploadScreen({super.key, required this.currentUser});

  @override
  State<UploadScreen> createState() => _UploadScreenState();
}

class _UploadScreenState extends State<UploadScreen> {
  TextEditingController locationController = TextEditingController();
  TextEditingController captionController = TextEditingController();
  XFile? imageFile;
  bool isUploading = false;
  String postId = const Uuid().v4();

  handleTakePhoto() async {
    Navigator.pop(context);
    imageFile = await ImagePicker().pickImage(
      source: ImageSource.camera,
      maxHeight: 675,
      maxWidth: 960,
    );
    setState(() {
      imageFile = imageFile;
    });
  }

  handleImageFromGallery() async {
    Navigator.pop(context);
    imageFile = await ImagePicker().pickImage(source: ImageSource.gallery);
    setState(() {
      imageFile = imageFile;
    });
  }

  selectImage(parentContext) {
    return showDialog(
        context: parentContext,
        builder: (context) {
          return SimpleDialog(
            title: const Text('Create Post'),
            children: [
              SimpleDialogOption(
                onPressed: handleTakePhoto,
                child: const Text('Photo with Camera'),
              ),
              SimpleDialogOption(
                onPressed: handleImageFromGallery,
                child: const Text('Image from Gallery'),
              ),
              SimpleDialogOption(
                child: const Text('Cancel'),
                onPressed: () => Navigator.pop(context),
              ),
            ],
          );
        });
  }

  Container buildSplash() {
    return Container(
      color: Theme.of(context).colorScheme.secondary.withOpacity(0.6),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          SvgPicture.asset(
            'assets/images/upload.svg',
            height: 260.0,
          ),
          Padding(
            padding: const EdgeInsets.only(top: 20.0),
            child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.deepOrange,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8.0),
                ),
              ),
              onPressed: () => selectImage(context),
              child: const Text(
                "Upload Image",
                style: TextStyle(color: Colors.white, fontSize: 22.0),
              ),
            ),
          ),
        ],
      ),
    );
  }

  clearImage() {
    setState(() {
      imageFile = null;
    });
  }

  compressImage() async {
    final tempDir = await getTemporaryDirectory();
    final pathe = tempDir.path;
    im.Image? imgFile = im.decodeImage(await imageFile!.readAsBytes());
    final compressedImageFile = File('$pathe/img_$postId.jpg')
      ..writeAsBytesSync(im.encodeJpg(imgFile!, quality: 85));
    setState(() {
      imageFile = XFile(compressedImageFile.path);
    });
  }

  uploadImage(XFile? imageFile) async {
    UploadTask uploadTask =
        reference.child("post_$postId.jpg").putFile(File(imageFile!.path));
    TaskSnapshot storageSnapshot = await uploadTask;
    String downloadUrl = await storageSnapshot.ref.getDownloadURL();
    return downloadUrl;
  }

  createPostInFirestore(
      {String? mediaUrl, String? location, String? description}) {
    postsRef
        .doc(widget.currentUser!.id)
        .collection("userPosts")
        .doc(postId)
        .set({
      "postId": postId,
      "ownerId": widget.currentUser!.id,
      "username": widget.currentUser!.username,
      "mediaUrl": mediaUrl,
      "description": description,
      "location": location,
      "timestamp": timeStamp,
      "likes": {},
    });
    captionController.clear();
    locationController.clear();
    setState(() {
      imageFile = null;
      isUploading = false;
    });
  }

  handleSubmitPost() async {
    setState(() {
      isUploading = true;
    });
    await compressImage();
    String mediaUrl = await uploadImage(imageFile);
    createPostInFirestore(
      mediaUrl: mediaUrl,
      location: locationController.text,
      description: captionController.text,
    );
  }

  Scaffold buildUploadForm() {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white70,
        leading: IconButton(
          icon: const Icon(Icons.arrow_back, color: Colors.black),
          onPressed: clearImage,
        ),
        title: const Text(
          'Caption Post',
          style: TextStyle(color: Colors.black),
        ),
        actions: [
          TextButton(
            onPressed: () {},
            // isUploading ? null : () => handleSubmitPost(),
            child: const Text(
              'Post',
              style: TextStyle(
                  color: Colors.blueAccent,
                  fontWeight: FontWeight.bold,
                  fontSize: 20.0),
            ),
          ),
        ],
      ),
      body: ListView(
        children: [
          isUploading ? linearProgress() : const Text(""),
          SizedBox(
            height: 220,
            width: MediaQuery.of(context).size.width * 0.8,
            child: Center(
              child: AspectRatio(
                aspectRatio: 16 / 9,
                child: Container(
                  decoration: BoxDecoration(
                    image: DecorationImage(
                      image: FileImage(File(imageFile!.path)),
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
              ),
            ),
          ),
          const Padding(
            padding: EdgeInsets.only(top: 10.0),
          ),
          ListTile(
            leading: CircleAvatar(
              backgroundImage:
                  CachedNetworkImageProvider(widget.currentUser!.photoUrl),
            ),
            title: SizedBox(
              width: 250.0,
              child: TextField(
                controller: captionController,
                decoration: const InputDecoration(
                  hintText: "Write a caption...",
                  border: InputBorder.none,
                ),
              ),
            ),
          ),
          const Divider(),
          ListTile(
            leading: const Icon(
              Icons.pin_drop,
              color: Colors.orange,
              size: 35.0,
            ),
            title: SizedBox(
              width: 250,
              child: TextField(
                controller: locationController,
                decoration: const InputDecoration(
                  hintText: 'Where was this photo taken?',
                  border: InputBorder.none,
                ),
              ),
            ),
          ),
          Container(
            width: 200,
            height: 100.0,
            alignment: Alignment.center,
            child: ElevatedButton.icon(
              onPressed: () {},
              icon: const Icon(
                Icons.my_location,
                color: Colors.white,
              ),
              label: const Text(
                'User Current Location',
                style: TextStyle(color: Colors.white),
              ),
            ),
          )
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return imageFile == null ? buildSplash() : buildUploadForm();
  }
}
问题分析
  1. 核心原因:Firebase Storage和Firestore操作需要用户认证令牌,但当前没有已登录的Firebase用户,导致无法获取有效令牌完成请求。
  2. 代码隐患:页面接收了currentUser参数,但未验证该用户是否通过Firebase Auth登录;Post按钮的点击事件被注释,实际无法触发上传流程;初始化或其他环节已尝试调用需要认证的Firebase操作,触发警告。
解决方案

1. 前置验证用户登录状态

在导航到UploadScreen前,检查Firebase Auth当前用户状态,仅允许登录用户访问:

// 导航前的检查逻辑
final firebaseUser = FirebaseAuth.instance.currentUser;
if (firebaseUser == null) {
  // 跳转到登录页面
  Navigator.push(context, MaterialPageRoute(builder: (_) => LoginScreen()));
} else {
  // 传入当前用户模型
  Navigator.push(context, MaterialPageRoute(builder: (_) => UploadScreen(currentUser: yourUserModel)));
}

2. 上传流程中添加认证检查

修改handleSubmitPost方法,先验证用户登录状态:

handleSubmitPost() async {
  final firebaseUser = FirebaseAuth.instance.currentUser;
  if (firebaseUser == null) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请先登录')),
    );
    setState(() => isUploading = false);
    return;
  }

  setState(() {
    isUploading = true;
  });
  await compressImage();
  String mediaUrl = await uploadImage(imageFile);
  createPostInFirestore(
    mediaUrl: mediaUrl,
    location: locationController.text,
    description: captionController.text,
  );
}

3. 修复Post按钮点击事件

恢复buildUploadForm中Post按钮的onPressed逻辑:

TextButton(
  onPressed: isUploading ? null : handleSubmitPost,
  child: const Text(
    'Post',
    style: TextStyle(
        color: Colors.blueAccent,
        fontWeight: FontWeight.bold,
        fontSize: 20.0),
  ),
),

4. 处理App Check警告(可选)

若启用了Firebase App Check,需完成以下配置:

  • 在Firebase控制台启用App Check,为Flutter应用配置调试令牌(开发环境)
  • 在main函数中初始化App Check:
// main函数中添加
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate(
  webRecaptchaSiteKey: 'YOUR_RECAPTCHA_SITE_KEY',
  androidProvider: AndroidProvider.debug, // 生产环境切换为AndroidProvider.playIntegrity
);

内容的提问来源于stack exchange,提问作者Fred_Wolfe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:44:55