I am trying to create a social media app; I want to see my posts in profile page, I can only see them for a second, then they are replaced by no posts
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:google_fonts/google_fonts.dart';
import 'auth_service.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State createState() => _ProfilePageState();
}
class _ProfilePageState extends State {
final auth = AuthService();
final db = FirebaseFirestore.instance;
final usernameCtrl = TextEditingController();
final bioCtrl = TextEditingController();
String gender = "Not set";
/* ================= EDIT MODAL ================= */
void openEditModal(Map? data) {
usernameCtrl.text = data?['username'] ?? "";
bioCtrl.text = data?['bio'] ?? "";
gender = data?['gender'] ?? "Not set";
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "Edit",
barrierColor: Colors.black.withOpacity(0.4),
transitionDuration: const Duration(milliseconds: 250),
pageBuilder: (_, __, ___) {
return Center(
child: Material(
color: Colors.transparent,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 18),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Edit Profile",
style: GoogleFonts.montserrat(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 16),
_field("Username", usernameCtrl),
const SizedBox(height: 10),
_field("Bio", bioCtrl, maxLines: 2),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(30),
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: gender,
isExpanded: true,
items: const [
DropdownMenuItem(value: "Not set", child: Text("Not set")),
DropdownMenuItem(value: "Male", child: Text("Male")),
DropdownMenuItem(value: "Female", child: Text("Female")),
DropdownMenuItem(value: "Other", child: Text("Other")),
],
onChanged: (val) => setState(() {
gender = val ?? "Not set";
}),
),
),
),
const SizedBox(height: 18),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
),
onPressed: () async {
await db.collection('users').doc(auth.uid).set({
"username": usernameCtrl.text.trim(),
"bio": bioCtrl.text.trim(),
"gender": gender,
"email": auth.email,
}, SetOptions(merge: true));
Navigator.pop(context);
setState(() {});
},
child: Text(
"Save",
style: GoogleFonts.montserrat(color: Colors.white),
),
),
),
],
),
),
),
);
},
);
}
Widget _field(String hint, TextEditingController ctrl, {int maxLines = 1}) {
return TextField(
controller: ctrl,
maxLines: maxLines,
style: GoogleFonts.montserrat(),
decoration: InputDecoration(
hintText: hint,
filled: true,
fillColor: const Color(0xFFF5F5F5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide: BorderSide.none,
),
),
);
}
/* ================= SAFE IMAGE ================= */
Widget safeImage(String url) {
if (url.isEmpty) {
return const ColoredBox(color: Colors.black12);
}
return Image.network(
url,
fit: BoxFit.cover,
// prevents crash / broken UI
errorBuilder: (_, __, ___) => const ColoredBox(
color: Colors.black12,
child: Center(child: Icon(Icons.broken_image)),
),
);
}
/* ================= UI ================= */
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: StreamBuilder(
stream: db.collection('users').doc(auth.uid).snapshots(),
builder: (context, userSnap) {
if (!userSnap.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data =
userSnap.data!.data() as Map? ?? {};
final username = data['username'] ?? "No username";
final bio = data['bio'] ?? "No bio yet";
final gender = data['gender'] ?? "Not set";
final email = data['email'] ?? auth.email;
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// HEADER
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Profile",
style: GoogleFonts.montserrat(
fontSize: 28,
fontWeight: FontWeight.w700,
),
),
Text(
"Your account space",
style: GoogleFonts.montserrat(
fontSize: 13,
color: Colors.black54,
),
),
],
),
GestureDetector(
onTap: () => openEditModal(data),
child: const Icon(Icons.edit),
)
],
),
const SizedBox(height: 25),
/// PROFILE CARD
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(24),
),
child: Column(
children: [
const CircleAvatar(
radius: 40,
backgroundColor: Colors.black,
child: Icon(Icons.person, color: Colors.white),
),
const SizedBox(height: 10),
Text(
username,
textAlign: TextAlign.center,
style: GoogleFonts.montserrat(
fontSize: 22,
fontWeight: FontWeight.w800,
),
),
Text(email),
const SizedBox(height: 10),
Text(bio, textAlign: TextAlign.center),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(20),
),
child: Text(
gender,
style: const TextStyle(color: Colors.white),
),
)
],
),
),
const SizedBox(height: 25),
Text(
"Posts",
style: GoogleFonts.montserrat(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
/// 🔥 FIXED STREAM (NO LOADER FLICKER BUG)
StreamBuilder(
stream: db
.collection("posts")
.where("userId", isEqualTo: auth.uid)
.orderBy("createdAt", descending: true)
.snapshots(),
builder: (context, postSnap) {
// ONLY SHOW LOADER IF NO DATA EVER EXISTS
if (postSnap.connectionState ==
ConnectionState.waiting &&
!postSnap.hasData) {
return const Center(
child: CircularProgressIndicator());
}
if (!postSnap.hasData ||
postSnap.data!.docs.isEmpty) {
return const Center(child: Text("No posts yet"));
}
final posts = postSnap.data!.docs;
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: posts.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 6,
mainAxisSpacing: 6,
),
itemBuilder: (_, i) {
final d =
posts[i].data() as Map;
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Stack(
fit: StackFit.expand,
children: [
safeImage(d['mediaUrl'] ?? ""),
if (d['mediaType'] == "video")
const Center(
child: Icon(
Icons.play_circle_fill,
color: Colors.white,
size: 30,
),
),
],
),
);
},
);
},
),
],
),
);
},
),
),
);
}
}
💡 What I expected
Smooth feed like Instagram
Images load once and stay stable
No flickering loader after initial load
❌ What is happening
Loader appears even after snapshot has data
UI rebuild seems to reset image state
Images briefly render then disappear/reload
This is my code guys , can you please help ,I will be very thankful