How to use StreamBuilder to listen for a document change in Firestore
I have reviewed several SO posts, reviewed the Dart language docs on Streams and also reviewed a few articles I found searching Google. Unfortunately, I am still puzzled. My goal is to refactor the code below to use a StreamBuilder. It has been suggested that I could improve my code even further by using a StreamBuilder but after a few days of trying I honestly can't figure out what to do or where to begin.
I want the user interface and the Firestore database to react to user taps on a switch tile. The boolean value in Firestore should toggle when the user clicks the switch and the switch tile user interface should update accordingly.
The code below works just fine but it doesn't use a StreamBuilder. Links to SO or documentation are more than welcome as I am happy to read and learn. Thanks in advance for any help.
class InterestsFitnessTile extends StatefulWidget {
const InterestsFitnessTile({
Key? key,
}) : super(key: key);
@override
State<InterestsFitnessTile> createState() => _InterestsFitnessTileState();
}
class _InterestsFitnessTileState extends State<InterestsFitnessTile> {
bool isFitnessActive = true;
final String? currentSignedInUserID = Auth().currentUser?.uid;
Future<void> _updateFitnessSetting() async {
await Auth()
.userInterestsSettingsReference
.doc(currentSignedInUserID)
.update({
AuthString.fitness: isFitnessActive,
});
setState(() {
isFitnessActive = !isFitnessActive;
});
}
@override
Widget build(BuildContext context) {
return SwitchListTileSliver(
icon: Provider.of<InterestsPageProvider>(context).isFitnessTurnedOff
? Icons.thumb_down
: Icons.thumb_up,
onChanged: (value) {
final provider = Provider.of<InterestsPageProvider>(
context,
listen: false,
);
provider.updateFitnessSettings(isOn: value);
_updateFitnessSetting();
},
subTitle: Provider.of<InterestsPageProvider>(context).isFitnessTurnedOff
? const Text(
SettingsPageString.fitnessOff,
)
: const Text(
SettingsPageString.fitnessOn,
),
title: SettingsPageString.fitness,
value: Provider.of<InterestsPageProvider>(context).isFitnessTurnedOff,
);
}
}
class InterestsPageProvider extends ChangeNotifier {
bool _currentFitness = false;
bool get isFitnessTurnedOff => _currentFitness == true;
void updateFitnessSettings({required bool isOn}) {
_currentFitness = !_currentFitness;
notifyListeners();
}
}
Solution 1:
Here are two examples in which I have used Streambuilder: Example 1:
class Messages extends StatelessWidget {
const Messages({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final currentUser = FirebaseAuth.instance.currentUser!.uid;
return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection("chat")
.orderBy("createdAt", descending: true)
.snapshots(),
builder: (ctx, snapShot) {
if (snapShot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
final chatDocs = snapShot.data!.docs;
return ListView.builder(
reverse: true,
itemBuilder: (ctx, i) => MessageBubble(
chatDocs[i]['text'],
chatDocs[i]['userName'],
chatDocs[i]['userId'] == currentUser,
chatDocs[i]['userImg'],
),
itemCount: chatDocs.length,
);
}
},
);}}
Example 2:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CarsProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, snapShot) =>
snapShot.hasData ? const HomePage() : const UserAuth(),
),
routes: {
HomePage.routeName: (_) => const HomePage(),
SearchPage.routeName: (_) => const SearchPage(),
SearchedItemDetail.routeName: (_) => const SearchedItemDetail(),
ForgotPassword.routeName: (_) => const ForgotPassword(),
AddItem.routeName: (_) => const AddItem(),
},
),
);
}
}