everyone, My project has 2 tabs, captain and first officer, in the main screen i have the tabs wrapped around with streamBuilder, so that it will wait until it gets data from firebase and then update it using provider, I haven't fully implemented provider yet as I am confused with riverPod usage in future. Anyways, the problem is it will load fine once, but when i go to the firstOfficer tab and back to captain tab, the init state of captain is called twice, and the streambuilder will download snapshot twice, which i can't figure out why , Is it the layout builder or sth..?
Here is the code
MainScreen calls to check if firebase data is downloaded in provider before updating the tabs
class MainScreen extends StatefulWidget {
static const String idScreen = "mainScreen";
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
@override
void initState() {
print('mainScreen initstate called');
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
final isSmallScreen = constraints.maxWidth < smallScreenSize;
final tabTextStyle =
Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: isSmallScreen ? 8 : 12,
);
final tabs = [
Tab(
text: isSmallScreen ? null : 'CAPTAIN',
icon: Icon(Bootstrap.person,
size: isSmallScreen ? smallIconSize : largeIconSize),
),
Tab(
text: isSmallScreen ? null : 'FIRST OFFICER',
icon: Icon(Icons.person_outline,
size: isSmallScreen ? smallIconSize : largeIconSize),
),
];
return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
toolbarHeight: isSmallScreen ? 10 : 20,
bottom: TabBar(
tabs: tabs,
onTap: (index) {
if (index == 12) {
FirebaseAuth.instance.signOut().then((value) {
Navigator.pushNamedAndRemoveUntil(
context, LoginScreen.idScreen, (route) => false);
});
} else {
setState(() {
_currentIndex = index;
});
}
},
labelStyle: tabTextStyle,
labelPadding:
EdgeInsets.symmetric(vertical: isSmallScreen ? 2 : 4),
indicatorPadding:
EdgeInsets.symmetric(horizontal: isSmallScreen ? 2 : 8),
isScrollable: false,
),
),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
StreamBuilder<List<Map<String, dynamic>>>(
stream:
Provider.of<FirebaseProvider>(context, listen: false)
.fetchCaptainData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return Captain();
}
},
),
FirstOfficer(),
],
),
),
);
},
),
);
}
}
this is the provider for firebase snapshot i added a print statemtn in fetchCaptainData
class FirebaseProvider extends ChangeNotifier {
List<Map<String, dynamic>> captainSnapShot = [];
List<Map<String, dynamic>> firstOfficerSnapShot = [];
List<Map<String, dynamic>> cabinCrewSnapShot = [];
static final CollectionReference captainCollection =
FirebaseFirestore.instance.collection('CAPTAIN_DATA');
Stream<List<Map<String, dynamic>>> fetchCaptainData() async* {
captainSnapShot = await getCaptainData();
print('captainProvider called');
//notifyListeners();
yield captainSnapShot;
}
static Future<List<Map<String, dynamic>>> getCaptainData() async {
final snapshot = await captainCollection.get();
final captainData =
snapshot.docs.map((doc) => doc.data() as Map<String, dynamic>).toList();
// print('Captain data fetched: $captainData');
return captainData;
}
Future<void> fetchFirstOfficerData() async {
firstOfficerSnapShot = await FirebaseServices
.getFirstOfficerData(); // Retrieves the captain data from Firebase.
notifyListeners();
}
Future<void> fetchCabinCrewData() async {
cabinCrewSnapShot = await FirebaseServices
.getCabinCrewData(); // Retrieves the captain data from Firebase.
notifyListeners();
}
}
Now this is the captain class
class Captain extends StatefulWidget {
const Captain({
Key? key,
}) : super(key: key);
@override
_CaptainState createState() => _CaptainState();
}
class _CaptainState extends State<Captain> {
@override
void initState() {
print('captain init state called');
super.initState();
i = 0;
// _updateGrid();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (captainData == null) {
_updateGrid();
}
}
@override
Widget build(BuildContext context) {
final CollectionReference captainCollection =
FirebaseFirestore.instance.collection('CAPTAIN_DATA');
firstOfficerData = Provider.of<FirebaseProvider>(context, listen: false)
.firstOfficerSnapShot;
return Scaffold(
////some code
));
}
the problem again, is this print statemtn when i go from the first officer tab to captain tab, the provider and initstate is called twice because of the streambuilder, i tried futurebuilder too, same thing, how can I fix it?
captainProvider called
captain init state called
captainProvider called
captain init state called
TabBarView calls the build method on the new child each time the tab changes. To prevent this, you can turn each tab child (Captain and First Officer) into a StatefulWidget that uses the AutomaticKeepAliveMixin. You just need to override
wantKeepAliveand set it to true.This should keep each child from getting rebuilt.
I also created a simple state management package as an alternative to Riverpod called code_on_the_rocks. It's fairly easy to run a future using that package and I have an example in the repo: https://github.com/CodeOTR/code_on_the_rocks/tree/main/samples/flutter_future_data