flutterfirebasefirebase-storage

image cannot be shown in Firebase with flutter but the URL link is valid


I am making an application with Firebase using Android Studio for my FYP. I am making a forum page for users to use. I am using file picker to retrieve and input images. The problem is images cannot be shown/loaded with a valid URL links in the webpage. My usually solution is to use the command "flutter run -d chrome --web-renderer html". Then every thing work perfectly. However after flutter is not support using web-renderer. Then the image cannot be loaded. Here my codes for my forum page:

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:file_picker/file_picker.dart';
import 'post_detail_page.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io';

Future<void> requestPermissions() async {
  if (kIsWeb) {
    print("Web does not require storage permissions.");
    return; // Skip permission request on web
  }

  if (Platform.isAndroid) {
    var status = await Permission.storage.request();
    if (status.isGranted) {
      print("Storage permission granted");
    } else {
      print("Storage permission denied");
    }
  }
}
class ForumPage extends StatefulWidget {
  const ForumPage({super.key});

  @override
  _ForumPageState createState() => _ForumPageState();
}

class _ForumPageState extends State<ForumPage> {
  void initState() {
    super.initState();
    requestPermissions(); // Request permission when the page initializes
  }
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final DatabaseReference _dbRef = FirebaseDatabase.instance.ref();

  final TextEditingController _titleController = TextEditingController();
  final TextEditingController _contentController = TextEditingController();

  File? _file;
  String? _fileUrl; // Declare this variable to hold the file URL

  // Pick a file (image or other types) from the device
  Future<void> _pickFile() async {
    try {
      final result = await FilePicker.platform.pickFiles(
        type: FileType.image,
        withData: kIsWeb, // Use withData for Web
      );

      if (result == null) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('No file selected.')),
        );
        return;
      }

      final file = result.files.single;

      if (kIsWeb && file.bytes != null) {
        // Handle file upload for Web (use bytes directly)
        final fileBytes = file.bytes!;
        final storageRef = FirebaseStorage.instance.ref().child('forum_files/${DateTime.now().millisecondsSinceEpoch}.jpg');
        final uploadTask = storageRef.putData(fileBytes);
        final snapshot = await uploadTask.whenComplete(() {});
        final fileUrl = await snapshot.ref.getDownloadURL();

        print('File uploaded successfully. URL: $fileUrl');
        setState(() {
          _fileUrl = fileUrl;
        });
      } else if (!kIsWeb && file.path != null) {
        // Handle file upload for Android/iOS
        final fileRef = FirebaseStorage.instance.ref().child('forum_files/${DateTime.now().millisecondsSinceEpoch}.jpg');
        final uploadTask = fileRef.putFile(File(file.path!));
        final snapshot = await uploadTask.whenComplete(() {});
        final fileUrl = await snapshot.ref.getDownloadURL();

        print('File uploaded successfully. URL: $fileUrl');
        setState(() {
          _fileUrl = fileUrl;
        });
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Failed to access file data.')),
        );
      }
    } catch (e) {
      print('Error picking file: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to pick file: $e')),
      );
    }
  }

  // Upload file to Firebase Storage
  Future<String?> _uploadFile(File file) async {
    try {
      final fileRef = FirebaseStorage.instance.ref().child('forum_files/${DateTime.now().millisecondsSinceEpoch}.jpg');

      final uploadTask = fileRef.putFile(file);
      final snapshot = await uploadTask.whenComplete(() {});
      final fileUrl = await snapshot.ref.getDownloadURL();
      return fileUrl;
    } catch (e) {
      print('Error uploading file: $e');
      return null;
    }
  }

  // Add a new post with a file to Firebase Realtime Database
  Future<void> createPost(String title, String content) async {
    final user = _auth.currentUser;
    if (user == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
            content: Text('You must be signed in to create a post.')),
      );
      return;
    }

    // Fetch the user's profile name from Realtime Database
    final databaseRef = FirebaseDatabase.instance.ref(
        'users/${user.uid}/profile');
    String profileName = 'Anonymous';

    try {
      final snapshot = await databaseRef.child('name').get();
      if (snapshot.exists) {
        profileName = snapshot.value as String;
      }
    } catch (e) {
      print('Error fetching profile name: $e');
    }

    // Construct the post data
    final postData = {
      'title': title,
      'content': content,
      'author': {'id': user.uid, 'name': profileName},
      'timestamp': DateTime
          .now()
          .millisecondsSinceEpoch, // Use local timestamp
    };

// Add the file URL to the post data if it's not null
    if (_fileUrl != null) {
      postData['fileUrl'] =
      _fileUrl!; // Use the '!' operator to assert that it's not null
    }

    try {
      final postRef = FirebaseDatabase.instance.ref('forum/posts').push();
      await postRef.set(postData);

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Post created successfully!')),
      );

      _titleController.clear();
      _contentController.clear();
      setState(() {
        _file = null; // Clear the file after posting
        _fileUrl = null; // Clear the file URL
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to create post: $e')),
      );
    }
  }

  // Build the UI for creating a new post
  Widget _buildCreatePostSection() {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('Create a Post', style: TextStyle(fontSize: 20)),
          const SizedBox(height: 10),
          TextField(
            controller: _titleController,
            decoration: const InputDecoration(labelText: 'Title'),
          ),
          const SizedBox(height: 10),
          TextField(
            controller: _contentController,
            decoration: const InputDecoration(labelText: 'Content'),
            maxLines: 5,
          ),
          const SizedBox(height: 10),
          ElevatedButton(
            onPressed: _pickFile,
            child: const Text('Select File'),
          ),
          if (_file != null)
            Padding(
              padding: const EdgeInsets.only(top: 8.0),
              child: Image.file(_file!, height: 100, width: 100, fit: BoxFit.cover),
            ),
          const SizedBox(height: 10),
          ElevatedButton(
            onPressed: () {
              final title = _titleController.text.trim();
              final content = _contentController.text.trim();
              if (title.isNotEmpty && content.isNotEmpty) {
                createPost(title, content);
              } else {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('Title and content cannot be empty.')),
                );
              }
            },
            child: const Text('Post'),
          ),
        ],
      ),
    );
  }

  // Build the list of posts
  Widget _buildPostList() {
    return StreamBuilder<DatabaseEvent>(
      stream: _dbRef.child('forum/posts').orderByChild('timestamp').onValue,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }

        if (!snapshot.hasData || snapshot.data?.snapshot.value == null) {
          return const Center(child: Text('No posts yet.'));
        }

        final postsData = (snapshot.data!.snapshot.value as Map).entries.toList();
        postsData.sort((a, b) => (b.value['timestamp'] as int).compareTo(a.value['timestamp'] as int));

        return ListView.builder(
          itemCount: postsData.length,
          itemBuilder: (context, index) {
            final post = postsData[index].value as Map;
            final title = post['title'] ?? 'No Title';
            final content = post['content'] ?? 'No Content';
            final author = post['author']?['name'] ?? 'Anonymous';
            final fileUrl = post['fileUrl'] as String?;

            return GestureDetector(
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => PostDetailPage(post: post), // Navigate to detail page
                  ),
                );
              },
              child: Card(
                margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      const SizedBox(height: 8),
                      Text(content),
                      const SizedBox(height: 8),
                      if (fileUrl != null)
                        Image.network(fileUrl,
                          width: double.infinity,
                          height: 200, // Set a limit
                          fit: BoxFit.contain,),
                      const SizedBox(height: 8),
                      Text('By: $author', style: const TextStyle(fontSize: 12, color: Colors.grey)),
                    ],
                  ),
                ),
              ),
            );
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Forum')),
      body: Column(
        children: [
          _buildCreatePostSection(),
          Expanded(child: _buildPostList()),
        ],
      ),
    );
  }
}

the result of the tested webpage

Here is my error message:

The following NetworkImageLoadException was thrown resolving an image stream completer: HTTP request failed, statusCode: 0, https://firebasestorage.googleapis.com/v0/b/traveler-teaming-up-app.appspot.com/o/forum_files%2F1737360405321.jpg?alt=media&token=91d8d438-5f60-425a-8a48-ee12f79a37fc

When the exception was thrown, this was the stack:

Image provider: NetworkImage("https://firebasestorage.googleapis.com/v0/b/traveler-teaming-up-app.appspot.com/o/forum_files%2F1737360405321.jpg?alt=media&token=91d8d438-5f60-425a-8a48-ee12f79a37fc", scale: 1.0)

Image key: NetworkImage("https://firebasestorage.googleapis.com/v0/b/traveler-teaming-up-app.appspot.com/o/forum_files%2F1737360405321.jpg?alt=media&token=91d8d438-5f60-425a-8a48-ee12f79a37fc", scale: 1.0)

If u need my entire code, comment below.


Solution

  • It is probably a CORS issue.

    Using google console cloud shell.

    cat > cors.json << 'EOF'
    [
      {
        "origin": ["*"],
        "method": ["GET"],
        "maxAgeSeconds": 3600
      }
    ]
    EOF
    
    gsutil cors set cors.json gs://YOUR-BUCKET-NAME.appspot.com
    
    gsutil cors get gs://YOUR-BUCKET-NAME.appspot.com