I have a streamProvider that contains all of the documents from a collection.
I want a StreamProvider that contains only certain documents. Can I use .where('agency', isEqualTo: 'some name'? Can this be done and if so how do I do it? Below is my current code:
'''
// From the main.dart
return MultiProvider(
providers: [
StreamProvider(create: (context) => firestoreService.getTrxns()),
],
child: MaterialApp(
initialRoute: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
Home_Screen.id: (context) => Home_Screen(),
},
/// This is from a Services.dart file
Stream<List<Trxns>> getTrxns() {
return _db.collection('trxns').snapshots().map((snapshot) => snapshot.docs
.map((document) => Trxns.fromFirestore(document.data()))
.toList());
}
'''
That's exactly how you would do it:
_db.collection('trxns').where('agency', isEqualTo:
'some name').snapshots().map()
Related
I have a list of routes with more than 50 lines and I wish to simplify it because it is very long and messy. Below is the example of current routes. Is there anyway I can extract it into separate files? For example, put //2 in routes2.dart file and //3 in routes3.dart file? How do I call them back in MyApp?
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider( create: (_) => SettingsNotifier()),
ChangeNotifierProvider(create: (_) => InterstitialTimerNotifier()),
ChangeNotifierProvider(create: (_) => InterstitialReadyNotifier()),
ChangeNotifierProvider(create: (_) => PolicyNotifier()),
],
child: MaterialApp(
themeMode:ThemeMode.system,
theme: ThemeClass.lightTheme,
darkTheme: ThemeClass.darkTheme,
initialRoute: HomeScreen.id,
routes: {
//1. Home
HomeScreen.id: (context) => HomeScreen(),
//2. Puasa
PuasaScreen.id: (context) => PuasaScreen(),
DoaBerbuka.id: (context) => DoaBerbuka(),
NiatPuasa.id: (context) => NiatPuasa(),
WaktuMustajabDoa.id: (context) => WaktuMustajabDoa(),
PuasaPengenalanScreen.id: (context) => PuasaPengenalanScreen(),
PuasaReference.id: (context) => PuasaReference(),
//3. Solat Tarawih
TarawihScreen.id: (context) => TarawihScreen(),
TarawihPengenalanScreen.id: (context) => TarawihPengenalanScreen(),
TarawihNiat.id:(context) => TarawihNiat(),
TarawihTakrif.id: (context) => TarawihTakrif(),
TarawihDalilScreen.id: (context) => TarawihDalilScreen(),
TarawihHukum.id: (context) => TarawihHukum(),
TarawihReference.id: (context) => TarawihReference(),
TarawihWaktuSolat.id: (context) => TarawihWaktuSolat(),
TarawihKelebihanScreen.id: (context) => TarawihKelebihanScreen(),
TarawihBilanganRakaat.id: (context) => TarawihBilanganRakaat(),
Tarawih8Screen.id: (context) => Tarawih8Screen(),
Tarawih8TatacaraScreen.id: (context) => Tarawih8TatacaraScreen(),
//4. Solat Witir
Tarawih8WitirScreen.id: (context) => Tarawih8WitirScreen(),
Tarawih8WitirRakaat1.id: (context) => Tarawih8WitirRakaat1(),
Tarawih8WitirRakaat2.id: (context) => Tarawih8WitirRakaat2(),
Tarawih8WitirRakaat3.id: (context) => Tarawih8WitirRakaat3(),
TarawihTahlil.id: (context) => TarawihTahlil(),
//There're more routes after this
}),
);
}
}
routes: takes Map<String, WidgetBuilder>, you can create
a dart file and create a map like,
final Map<String, WidgetBuilder> route2 = {
PuasaScreen.id: (context) => PuasaScreen(),
.....
};
same goes for routes3.dart.
and you can add theses routes on MaterialApp like
return MaterialApp(
routes: {
HomeScreen.id: (context) => HomeScreen(),
...route2, /// variable name
...route3,
},
);
This is the updated code after applying suggestions from #OMiShah.
I created new dart files for the routes. For example, route_puasa.dart:
import ...
Map routes_puasa = {
PuasaScreen.id: (context) => PuasaScreen(),
DoaBerbuka.id: (context) => DoaBerbuka(),
NiatPuasa.id: (context) => NiatPuasa(),
WaktuMustajabDoa.id: (context) => WaktuMustajabDoa(),
PuasaPengenalanScreen.id: (context) => PuasaPengenalanScreen(),
PuasaReference.id: (context) => PuasaReference(),
};
Then call it in MyApp using ... Spread Operator
routes: {
...routes_puasa,
...route_tarawih,
...routes_tarawih20,
...routes_zakat,
}
Works perfectly!
I am still learning flutter/Dart and creating an app in the process.
Now, I am looking at my code and thinking that I am working too hard by not using some StreamProviders I create in my main.dart file. I did this at the very beginning of my journey so I really didn't know what I was doing. I was just following a tutorial.
So, in my main.dart I created a multiprovider and some StreamProviders inside that.
Widget build(BuildContext context) {
final firestoreService = FirestoreService();
Provider.debugCheckInvalidValueType = null;
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AgentProvider()),
ChangeNotifierProvider(create: (context) => AgencyProvider()),
ChangeNotifierProvider(create: (context) => TrxnProvider()),
ChangeNotifierProvider(create: (context) => EventProvider()),
// The following providers are for retrieving the data for editing purposes
// The stream providers are only called once but they can be accessed
// anywhere in the app.
StreamProvider(create: (context) => firestoreService.getAgents(), initialData: []),
StreamProvider(create: (context) => firestoreService.getAgency(), initialData: []),
StreamProvider(
create: (context) => firestoreService.getAgencyTrxns(context), initialData: []),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginScreen(),
),
);
}
As you can see, I added notes stating that I can access the streams anywhere in my code but I have been creating new streams throughout. Yes, this is embarrassing but I am learning.
Here are the 3 functions used to create the StreamProviders above
Stream<List<Agents>> getAgents() {
return _db.collection('agents').snapshots().map((snapshot) => snapshot.docs
.map((document) => Agents.fromFirestore(document.data()))
.toList());
}
Stream<List<Agency>> getAgency() {
return _db.collection('agency').snapshots().map((snapshot) => snapshot.docs
.map((document) => Agency.fromFirestore(document.data()))
.toList());
}
Stream<QuerySnapshot> getAgencyTrxns(BuildContext context) async* {
yield* FirebaseFirestore.instance
.collection('agency').doc(globals.agencyId)
.collection('trxns')
.where('trxnStatus', isNotEqualTo: 'Closed')
.snapshots();
}
Had I realized what I have done it would have saved me a lot of work...I think.
Now, I am trying to learn how to use these StreamProviders in my code. How do I use them in a StreamBuilder? Currently, this is what I am doing and I don't think I am using the StreamProviders I create in my main.dart.
child: StreamBuilder(
stream: _db.collection('agency').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
} else {
I think I need to use consume. I am watching a YouTube video but it is not very clear. I would really like to use the StreamProviders I already have rather than create new streams and use memory.
Any help you can provide or if you can point me to a great tutorial that would be even better.
I am unable to update my Ui
//// HERE I AM GETTING THE UPDATE DATA IN CONSOLE BUT UNABLT TO UPDATE INSIDE MY UI
My NotificationCountClass
class NotificationCount extends ChangeNotifier{
var count;
NotificationCount({
this.count =0,
});
addNotificationCount(){
count++;
notifyListeners();
print("Notification Count $count");
}
}
main : here i wrap the widget inside multiprovider so that i can use it any-where in my app
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
builder: (context, appService, child) {
return GetMaterialApp(
title: AppStrings.APP_TITLE,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.dartTheme,
navigatorKey: GlobalVariable.navigatorKey,
supportedLocales: [
Locale('en'),
],
localizationsDelegates: [
CountryLocalizations.delegate,
],
themeMode: appService.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: AppRouter.SPLASH_SCREEN,
onGenerateRoute: AppRouter.router.generator,
// routes: {
// "/":(context) =>Home(),
// "/AppChat" : (context) => AppChat(),
// },
debugShowCheckedModeBanner: false,
// home: AppChat(),
);
},
),
);
// Using State with Consumer widget so that only required wiget rebuild
Consumer<NotificationCount>(
builder: (context, value, child) {
var count = value.count;
print("Count of Not : $count");
return Text(
"$count",
style: TextStyle(
color: Colors.white,
),
);
},
),
getting NotificationCount class with provider but still unable to update UI
final notificationCount = Provider.of<NotificationCount>(context , listen: false);
I could reproduce your issue on my side, and could fix it just by using the builder of MultiProvider instead of child.
Instead of
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
write somthing like:
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
builder: (context, _) => Consumer<AppService>(
...
I have an app where I use StreamProvider to fetch data from Firestore & pass it around in the app. While it works as I want, the problem arises when I log out & sign in again from a different user ID, it still shows the data from the first user. What do I do to make sure the StreamProvider disposes the current value when a user logs out, and refetches the proper user data when a new user signs in? Here's my code
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final db = Database();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider(
// key: ObjectKey(FirebaseAuth.instance.currentUser.uid),
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
),
ChangeNotifierProvider<AppointmentProvider>(
create: (_) => AppointmentProvider(),
),
StreamProvider(
catchError: (context, error) {
print(error);
},
create: (BuildContext context) => db.getAppointments(),
),
],
child: MaterialApp(
key: GlobalObjectKey(context),
debugShowCheckedModeBanner: false,
title: 'Hospital App',
theme: appTheme,
home: AuthenticationWrapper(),
),
);
}
}
db.getAppointments() method
Stream<List<AppointmentModel>> getAppointments() {
return _firebaseFirestore
.collection("DoctorAppointments")
.doc(uid)
.collection("DoctorAppointmentsList")
.snapshots()
.map((snapShot) => snapShot.docs
.map((document) => AppointmentModel.fromJson(document.data()))
.toList());
}
I have added a new bloc SyncBloc to a MultiBlocProvider (flutter_bloc package) that creates all the blocs needed for MapScreen and for some it also adds Events needed to present data in the screen itself.
The problem is that while location events (for LocationBloc) are added correctly from the MultiBlocProvider itself, sync events (for SyncBloc) are not. If I instead add them from MapScreen's MultiBlocListener as
BlocProvider.of<SyncBloc>(context).add(SyncLanguages());
they work as expected so looks like SyncBloc has been provided correctly.. Can you spot what I'm doing wrong with the newer SyncBloc or point me in the right direction?
As always thank you very much for your time and help.
This is the MultiBlocProvider in main():
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
const AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('it', ''),
const Locale('es', ''),
],
localeResolutionCallback:
(Locale locale, Iterable<Locale> supportedLocales) {
for (Locale supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode ||
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
}
}
return supportedLocales.first;
},
debugShowCheckedModeBanner: false,
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
if (state is Authenticated) {
return MultiBlocProvider(
providers: [
BlocProvider<TrackingBloc>(
create: (context) => TrackingBloc(),
),
BlocProvider<DirectionsBloc>(
create: (context) => DirectionsBloc(),
),
BlocProvider<GeoBloc>(
create: (context) => GeoBloc(),
),
BlocProvider<RouteBloc>(
create: (context) => RouteBloc(),
),
BlocProvider<SchedulerBloc>(
create: (context) => SchedulerBloc(),
),
BlocProvider<CheckerBloc>(
create: (context) => CheckerBloc(),
),
BlocProvider<LocationBloc>(
create: (context) => LocationBloc(
mapRepository: _mapRepository,
)
..add(GetLocationStream())
..add(GetLocation())
..add(GetIsoLocationUser())),
BlocProvider<SyncBloc>(
create: (context) =>
SyncBloc()..add(SyncLanguages())..add(SyncIcons())),
BlocProvider<AlertBloc>(create: (context) {
return AlertBloc(
alertRepository: _alertRepository,
);
}),
],
child: MapScreen(
// mapRepository: _mapRepository,
user: state.user,
// alertRepository: FirebaseAlertRepository(),
),
);
}
return SplashScreen();
},
),
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
);
SyncEvent:
abstract class SyncEvent {
const SyncEvent();
#override
List<Object> get props => [];
}
class SyncLanguages extends SyncEvent {}
class SyncIcons extends SyncEvent {}
and SyncBloc:
class SyncBloc extends Bloc<SyncEvent, SyncState> {
#override
SyncState get initialState => InitialState();
Stream<SyncState> mapEventToState(SyncEvent event) async* {
if (event is SyncLanguages) {
print('SyncLanguages received');
}
if (event is SyncIcons) {
print('SyncIcons received');
}
}
}
The problem has to do with the BlocProvider's create method being lazy by default. So until the .of method is called BlocProvider doesn't create the bloc. To make it create the bloc immediately just set lazy: parameter to false.
BlocProvider<LocationBloc>(
lazy: false,
create: (context) => LocationBloc(
mapRepository: _mapRepository,
)
..add(GetLocationStream())
..add(GetLocation())
..add(GetIsoLocationUser())),
BlocProvider<SyncBloc>(
lazy: false,
create: (context) => SyncBloc()
..add(SyncLanguages())
..add(SyncIcons())),
This actually works, though AuthenticationBloc and LocationBloc events were sent even without the lazy parameter set to false. Still gotta check why that but I guess those two blocs are being created respectively by a BlocBuilder and a BlocListener. I'll edit the answer as soon as I find out for sure.