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()),
],
),
);
}
}
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.
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