How to sync stream variable in BlocState with the TextFormField - flutter

I am not able to figure out the right architecture of getting this done using firestore streams and flutter_bloc.
My goal :
I have a logged in user and I want to store the user details in the collection with the document id same as logged in user id and want to listen to changes, Throughout the program.
Meanwhile i would want to even store user details (if doesn't exist) and update user details.
My approach:
I am having a usermodel in UserState and i am listening to userModel via state using BlocBuilder.
Problem:
As i am using BlocBuilder my setState isn't working and TextFormField isn't working as it says beginBatchEdit on inactive InputConnection
Code:
UserCubit.dart
class UserCubit extends Cubit<UserState> {
final _firestore = FirebaseFirestore.instance;
final User? _currentUser = FirebaseAuth.instance.currentUser;
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore --> listening to stream and updating state
.collection("sample")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? emit(UserExists(sample: SampleModel.fromjson(event.data()!, event.id)))
: {emit(UserNotExists())};
});
}
Future<void> addSampleUser({required SampleModel sample}) async {
emit(UserSideLoadingState());
_firestore
.collection('sample')
.doc(_currentUser?.uid)
.set(sample.toJson())
.then((value) => emit(UserSavedUpdatedState()));
}
}
UserState.dart
abstract class UserState extends Equatable {
final SampleModel? sampleModel;
const UserState({this.sampleModel});
#override
List<Object?> get props => [sampleModel];
}
class UserExists extends UserState {
const UserExists({required SampleModel sample}) : super(sampleModel: sample);
}
Widget.dart (Save/Update User Details)
class _MyWidgetState extends State<MyWidget> {
// #override
var fullNameKey;
TextEditingController? fullNameController;
bool _formChecked = false;
TextEditingController? phoneNumberController;
Avatar? selectedAvatar;
#override
void initState() {
fullNameController = TextEditingController();
fullNameKey = GlobalKey<FormState>();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: BlocConsumer<UserCubit, UserState>(listener: (context, state) {
if (state is UserSavedUpdatedState) {
print("Saved/Updated User");
context.goNamed(Routes.profileMain);
}
}, builder: (context, state) {
// Here i am trying to get details from the state
selectedAvatar = state.sampleModel?.avatar == "boy"
? Avatar.boy
: state.sampleModel?.avatar == "boy"
? Avatar.girl
: null;
fullNameController!.text = state.sampleModel?.name ?? "";
if (state is UserMainLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return Scaffold(
body: Column(
children: [
Row(
children: [
IconButton(
onPressed: () {
setState(() {
selectedAvatar = Avatar.girl; -- > This doesn't work because of BlocBuilder
});
},
icon: const Icon(Icons.girl)),
IconButton(
onPressed: () {
setState(() {
selectedAvatar = Avatar.boy; -- > This doesn't work because of BlocBuilder
});
},
icon: const Icon(Icons.boy))
],
),
Form(
key: fullNameKey,
child: TextFormField(
// BlocBuilder even freezes TextFormField
autovalidateMode: _formChecked
? AutovalidateMode.always
: AutovalidateMode.disabled,
validator: (value) {
if (value == null ||
fullNameController?.text.trim() == "") {
return "Name cannot be empty";
}
if (value.length < 3) {
return "Username must be greater than 3 characters";
}
return null;
},
controller: fullNameController,
decoration: const InputDecoration(
labelText: "Full Name",
),
)).marginDown(),
FilledButton(
onPressed: () {
setState(() {
_formChecked = true;
});
if (fullNameKey.currentState!.validate() &&
selectedAvatar != null) {
SampleModel sample = SampleModel(
name: fullNameController!.text,
avatar: selectedAvatar == Avatar.boy ? "boy" : "girl");
BlocProvider.of<UserCubit>(context)
.addSampleUser(sample: sample);
}
},
child: const Text("Submit"),
)
],
));
}),
);
}
}
As soon as the submit button is clicked it erases the entire text and validator gets activated. Avatar selection doesn't work as well.
What is the best way to achieve the desired function using streams, flutter_bloc, Suggestions would be greatly appreciated

as far as I can see you pre-select the avatar based on the emitted state. However, I do not see that you return the selection via an event/function to the bloc/cubit. So this is needed in order to send the updated avatar with the next emit.
From what I can see, I would also possibly exchange the abstract class state with a class state implementing Equatable and the simply always copyWith the state for any updates. This way you always have the same UserState - no need for if and else if for state selection, however, the data of the state changes based on the situation. I think for a user bloc/cubit this makes the lifecycle a bit easier
UPDATE:
IconButton(
onPressed: () {
setState(() {
context.read<UserCubit>.updateUser(selectedAvatar: Avatar.boy);
selectedAvatar = Avatar.boy; -- > possibly no longer needed if returned from Cubit
});
},
icon: const Icon(Icons.boy))
As for the state management, a state can look like this:
class TaskListState extends Equatable {
const TaskListState({
this.status = DataTransStatus.initial,
this.taskList = const [],
this.filter,
this.error,
this.editThisTaskId,
});
final DataTransStatus status;
final List<TaskListViewmodel> taskList;
final TaskListFilter? filter;
final String? error;
final String? editThisTaskId;
TaskListState copyWith({
DataTransStatus Function()? status,
List<TaskListViewmodel> Function()? taskList,
TaskListFilter Function()? filter,
String Function()? error,
String? Function()? editThisTaskId,
}) {
return TaskListState(
status: status != null ? status() : this.status,
taskList: taskList != null ? taskList() : this.taskList,
filter: filter != null ? filter() : this.filter,
error: error != null ? error() : this.error,
editThisTaskId: editThisTaskId != null
? editThisTaskId() : this.editThisTaskId,
);
}
#override
List<Object?> get props => [
status,
taskList,
filter,
error,
editThisTaskId,
];
}
which you use - in this case with a Stream - like this:
await emit.forEach<dynamic>(
_propertiesRepository.streamTasks(event.propertyId),
onData: (tasks) {
return state.copyWith(
status: () => DataTransStatus.success,
taskList: () => List.from(
tasks.map((t) => TaskListViewmodel.fromDomain(t))),
);
},
onError: (_, __) {
return state.copyWith(
status: () => DataTransStatus.failure,
);
},
);

Related

Blocbuilder not rebuilding second time even though the state is changing

I'm trying to get a list of movies from api but when implementing infinite scroll pagination using ListView ScrollController infinite scroll is not working. I've checked the output from state also it is getting correctly when scroll reaches end of list but not updating in UI. only after i hot reload it shows in the UI.
Here is the Blocfile.
#injectable
class SearchMovieBloc extends Bloc<SearchMovieEvent, SearchMovieState> {
final SearchMovieRepo searchmovierepo;
SearchMovieBloc(this.searchmovierepo) : super(SearchMovieState.initial()) {
on<SearchMovieEvent>((event, emit) async {
final Either<MainFailures, List<MovieModel>> result =
await searchmovierepo.searchmovie(
moviequery: event.moviequery, page: event.page);
log(result.toString());
emit(result.fold(
(failure) =>
state.copyWith(isLoading: false, options: Some(Left(failure))),
(success) => state.copyWith(
isLoading: false,
movies: success,
options: Some(Right(success)))));
});
}
}
SearchMovieState
#freezed
class SearchMovieState with _$SearchMovieState {
const factory SearchMovieState(
{required bool isLoading,
required List<MovieModel> movies,
required Option<Either<MainFailures, List<MovieModel>>> options}) =
_SearchMovieState;
factory SearchMovieState.initial() =>
const SearchMovieState(isLoading: false, options: None(), movies: []);
}
SearchMovieEvent
#freezed
class SearchMovieEvent with _$SearchMovieEvent {
const factory SearchMovieEvent.searchmovie(
{required String moviequery, required int page}) = _SearchMovie;
}
and the UI
class SearchMovieList extends StatefulWidget {
TextEditingController text;
SearchMovieList({
Key? key,
required this.text,
}) : super(key: key);
#override
State<SearchMovieList> createState() => _SearchMovieListState();
It's been two days i've working on this issue hope someone helps me.
}
class _SearchMovieListState extends State<SearchMovieList> {
int page = 1;
ScrollController controller = ScrollController();
#override
void initState() {
controller.addListener(() {
if (controller.position.maxScrollExtent == controller.offset) {
page++;
BlocProvider.of<SearchMovieBloc>(context).add(
SearchMovieEvent.searchmovie(
moviequery: widget.text.text, page: page));
}
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<SearchMovieBloc, SearchMovieState>(
builder: (context, state) {
if (state.isLoading) {
return const Center(
child: CircularProgressIndicator(
color: orange,
),
);
} else if (state.movies.isEmpty && widget.text.text.isNotEmpty) {
return const Center(
child: Text(
"No Results to Show",
style: TextStyle(color: grey, fontSize: 25),
),
);
} else if (state.movies.isNotEmpty && widget.text.text.isNotEmpty) {
/* log(state.movies.toString()); */
return ListView.separated(
controller: controller,
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemBuilder: ((context, itemIndex) {
if (itemIndex < state.movies.length) {
return SearchTile(
ismovie: true,
overview: state.movies[itemIndex].overview!,
id: state.movies[itemIndex].movieid!,
heading: state.movies[itemIndex].title == null
? state.movies[itemIndex].name!
: state.movies[itemIndex].title!,
rating:
state.movies[itemIndex].rating!.toStringAsFixed(1),
image: state.movies[itemIndex].posterPath != null
? "$posterhead${state.movies[itemIndex].posterPath}"
: null,
year: state.movies[itemIndex].releasedate == null ||
state.movies[itemIndex].releasedate!.length < 5
? "_"
: state.movies[itemIndex].releasedate!
.substring(0, 4));
} else {
return const Center(
child: CircularProgressIndicator(color: orange));
}
}),
separatorBuilder: (context, index) => const Divider(
height: 4.0,
),
itemCount: state.movies.length);
} else {
return const SizedBox();
}
},
);
}
}
use the equatable package or write the == operator manually. However, I have not tried using the freezed package together with equatable
class SearchMovieState extends Equatable {
const SearchMovieState(
{required this.isLoading, required this.movies, required this.options});
final bool isLoading;
final List<MovieModel> movies;
final Option<Either<MainFailures, List<MovieModel>>> options;
#override
List<Object?> get props => [isLoading, movies, options];
}
I am using equatable. For my case, props wasn't enough to emit the same state. I had to include identityHashCode on props.
abstract class TestState extends Equatable {
const TestState();
#override
List<Object?> get props => [];
}
class ErrorState extends TestState{
final Map? message;
const ErrorState({this.message});
#override
List<Object?> get props => [message, identityHashCode(this)];
}
I recommend using Equatable https://pub.dev/packages/equatable to override the == operator. So instead of checking for the same address, Equatable checks if the object and its values are equal.
And you have to be sure that you are requesting more data and not the same data
I recommend you debug this line of code
final Either<MainFailures, List<MovieModel>> result = await searchmovierepo.searchmovie(moviequery: event.moviequery, page: event.page);
Just to be sure that the data is different
Use a fancy bool operator to trick the block to emit the state. use the below code.
In your state add a new field change state and initialize with false.
#freezed
class SearchMovieState with _$SearchMovieState {
const factory SearchMovieState(
{required bool isLoading,
required bool changeState, // add this field here
required List<MovieModel> movies,
required Option<Either<MainFailures, List<MovieModel>>> options}) =
_SearchMovieState;
factory SearchMovieState.initial() =>
const SearchMovieState(isLoading: false, changeState: false, options: None(), movies: []); // initialize the changefield with false
}
on the BlockFile invert the changeState on success
emit(result.fold(
(failure) =>
state.copyWith(isLoading: false, options: Some(Left(failure))),
(success) => state.copyWith(
isLoading: false,
changeState: !state.changeState, // toggle the changeStatefield
movies: success,
options: Some(Right(success)))));
This Worked for me.

How to handle Firebase Authchanges with fetching user model in Riverpod

Hello everyone I'am working on Firebase Auth with Riverpod.
I have a StreamProvider which it is listening FirebaseAuthChanges. This works fine.
And I have a UserProvider which it is modeling user from Firebase. (This works fine too but not on first start).
final myAccountProvider = StateNotifierProvider<UserNotifier, UserModel>(
(ref) {
return UserNotifier();
},
);
class UserNotifier extends StateNotifier<UserModel> {
UserNotifier()
: super(
UserModel(
uid: '',
name: '',
email: '',
bildirim: 0,
kullaniciAdi: '',
pphoto: '',
sehir: '',
isBaker: null,
isBakeryEmpty: null,
bakeryID: '',
bakerySetupTaskLevel: null,
),
) {
getUserFields();
debugPrint('USERNOTİFİER İNİTİALİZED');
}
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final FirebaseService _firebaseService = FirebaseService();
getUserFields() async {
UserModel model =
await _firebaseService.fetchMyDatas(_firebaseAuth.currentUser!.uid);
state = model;
}
}
Here where the problem has begun:
My MaterialApp's home property is returning AuthWrapper widget which it is controlling user auth.
class AuthWrapper extends ConsumerWidget {
const AuthWrapper({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authStateProvider);
final isBaker = ref.watch(myAccountProvider).isBaker;
return authState.when(
data: (user) {
if (user != null) {
if (isBaker == true) {
return const BakerRootView();
} else {
return const UserRootView();
}
} else {
return const LoginView();
}
},
error: (e, s) => Container(),
loading: () => const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
Let's take a look for this widget. When the app start first the UserNotifier (so myAccountProvider) returning error that Null check op. used on null value
Where is that error ? On this line:
await _firebaseService.fetchMyDatas(_firebaseAuth.currentUser!.uid);
I know the problem what is. When the app initializing, without any firebase.currentUser, the UserNotifier can't return getUserField function.
Then I was think about defining UserNotifier after (user != null) condition like this:
if (user != null) {
final isBaker= ref.watch(myAccountProvider).isBaker;
if (isBaker== true) {
print('Baker Root Builded')
return const BakerRootView();
} else {
print('User Root Builded')
return const UserRootView();
}
} else {
return const LoginView();
}
At the first look this work's fine. But if I hot restart my app, in console, I see print('Baker Root Builded') and print('User Root Builded') together. Why is this so bad?
Because if the internet connection is not fast enough, UserRootView will be shown to eachuser.
Is there a better solution for modeling user with Authentication or not ? Any document, any article to you can show me ? Or How can I fix this issue ?

Flutter: How to change state of sibling widget widget?

I have already tried the solution here. My code already depended on a class passed on a parent class.
import 'package:flutter/cupertino.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../widgets/cupertino_modal_button_row.dart';
import '../widgets/simple_widgets.dart';
import '../models/match.dart';
class AddScoreSection extends StatefulWidget {
AddScoreSection({
this.set,
this.partnerIds,
Key key,
}) : super(key: key);
final MatchSet set;
final List<String> partnerIds;
#override
_AddScoresState createState() => _AddScoresState();
}
class _AddScoresState extends State<AddScoreSection> {
String _partnerText;
#override
Widget build(BuildContext context) {
final set = widget.set;
final _hostId = FirebaseAuth.instance.currentUser.uid;
String _partnerId = set.hostTeam != null
? set.hostTeam.firstWhere(
(element) => element != FirebaseAuth.instance.currentUser.uid)
: null;
Future<String> _partnerName(String partnerId) async {
if (partnerId == null) {
return null;
}
final userData = await FirebaseFirestore.instance
.collection('users')
.doc(partnerId)
.get();
return userData['name']['full_name'];
}
print(widget.set.visitingGames);
return CupertinoFormSection(
header: const Text('Set'),
children: [
CupertinoModalButtonRow(
builder: (context) {
return CupertinoActionSheet(
title: _partnerText == null
? const Text('Select a Partner')
: _partnerText,
actions: widget.partnerIds
.map((partner) => CupertinoActionSheetAction(
child: FutureBuilder<String>(
future: _partnerName(partner),
builder: (ctx, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return SimpleWidget.loading10;
}
return Text(snapshot.data);
},
),
onPressed: () async {
final partnerTemp = await _partnerName(partner);
_partnerId = partner;
setState(() {
_partnerText = partnerTemp;
});
set.addToHostTeam = [_partnerId, _hostId];
set.addToVisitTeam = widget.partnerIds
.where((element) =>
widget.set.hostTeam.contains(element))
.toList();
print(set.hostTeam);
Navigator.of(context).pop();
},
))
.toList());
},
buttonChild:
_partnerText == null ? 'Select your Partner' : _partnerText,
prefix: 'Your Partner'),
ScoreEntryRow(
setsData: widget.set,
prefix: 'Team Score',
),
ScoreEntryRow(
prefix: 'Opponent Score',
setsData: widget.set,
isHostMode: false,
),
],
);
}
}
class ScoreEntryRow extends StatefulWidget {
ScoreEntryRow({
Key key,
#required this.setsData,
#required this.prefix,
this.isHostMode: true,
}) : super(key: key);
final MatchSet setsData;
final String prefix;
final bool isHostMode;
#override
_ScoreEntryRowState createState() => _ScoreEntryRowState();
}
class _ScoreEntryRowState extends State<ScoreEntryRow> {
#override
Widget build(BuildContext context) {
final ValueNotifier _games = widget.isHostMode
? ValueNotifier<int>(widget.setsData.hostGames)
: ValueNotifier<int>(widget.setsData.visitingGames);
List<int> scoreList = List.generate(9, (index) => index + 1);
return CupertinoModalButtonRow(
builder: (context) {
return SizedBox(
height: 300,
child: ListView.builder(
itemCount: scoreList.length,
itemBuilder: (context, i) {
return CupertinoButton(
child: Text(scoreList[i].toString()),
onPressed: () {
setState(() {
if (widget.isHostMode) {
widget.setsData.setHostGames = scoreList[i];
widget.setsData.setVisitGames = 9 - scoreList[i];
return;
}
widget.setsData.setVisitGames = scoreList[i];
widget.setsData.setHostGames = 9 - scoreList[i];
});
});
}),
);
},
buttonChild: widget.isHostMode
? widget.setsData.hostGames == null
? '0'
: widget.setsData.hostGames.toString()
: widget.setsData.visitingGames == null
? '0'
: widget.setsData.visitingGames.toString(),
prefix: widget.prefix,
);
}
}
The code for the custom class is:
class MatchSet {
MatchSet(this.setData);
final Map<String, dynamic> setData;
List<String> hostTeam;
List<String> visitingTeam;
int hostGames;
int visitingGames;
set addToHostTeam(List<String> value) {
if (setData.values.every((element) => element != null))
hostTeam = setData['host_team'];
hostTeam = value;
}
set addToVisitTeam(List<String> value) {
if (setData.values.every((element) => element != null))
visitingTeam = setData['visiting_team'];
visitingTeam = value;
}
set setHostGames(int value) {
if (setData.values.every((element) => element != null))
hostGames = setData['host_games'];
hostGames = value;
}
set setVisitGames(int value) {
if (setData.values.every((element) => element != null))
visitingGames = setData['visiting_games'];
visitingGames = value;
}
Map<List<String>, int> get matchResults {
return {
hostTeam: hostGames,
visitingTeam: visitingGames,
};
}
List<String> get winningTeam {
if (matchResults.values.every((element) => element != null)) {
return null;
}
return matchResults[hostTeam] > matchResults[visitingTeam]
? hostTeam
: visitingTeam;
}
String result(String userId) {
if (matchResults.values.every((element) => element != null)) {
return null;
}
if (winningTeam.contains(userId)) {
return 'W';
}
return 'L';
}
bool get isUpdated {
return hostGames != null && visitingGames != null;
}
}
The state does not change for the sibling as you can see in the video. I have also trying the whole button in a ValueListenableBuilder, nothing changes.
What am I doing wrong? By all my knowledge the sibling row widget should rebuild, but it is not rebuilding.
You can use multiple approaches
the solution that you point out is a callback function and it works fine ... you use a function that when it is called it would go back to its parent widget and do some stuff there(in your case setState)
but It does not seem that you are using this it.
the other one is using StateManagement. you can use GetX , Provider, RiverPod,... .
in this one, you can only rebuild the widget that you want but in the first solution, you have to rebuild the whole parent widget.
both solutions are good the second one is more professional and more efficient but i think the first one is a must-know too

I keep getting getters called on null when I try to update the user profile

Hello I'm new to flutter, and I'm trying to do a simple user profile screen for a user who logged in but I stumbled in some errors, the first one was for when I tried to use the StreamBuilder() where the stream didn't get any data from the getter in the UserProvider()(that's where I putted my BL) it kept saying getCurrentUserData() was called on null, so i just connected it directly to the UserService() and it worked, but then when I tried to edit the user info and have the TextFormField() be filled with the user data, via the initState() and have the fullNameController get the data from the UserModel() the error returned it keeps saying fullName was called on null! how do I resolve this can anyone point on where I'm going wrong about here?
P.S I'm following this tutorial to build this.
My StreamBuilder() connected to UserProvider:
return StreamBuilder<UserModel>(
stream: userProviderData.getCurrentUserData(),
builder: (context, snapshot) {})
My StreamBuilder() directly connected to UserService:
Directly connected to UserService
return StreamBuilder<UserModel>(
stream: userService.getCurrentUser(),
builder: (context, snapshot) {})
UserService() class:
// Get User
Stream<UserModel> getCurrentUser() {
return _db.collection('users').doc(_auth.currentUser.uid).snapshots().map(
(user) => UserModel.fromJson(user.data()),
);
}
// Add or Update User info
Future<void> saveUser(UserModel user) {
final _options = SetOptions(merge: true);
return _db.collection('users').doc(user.userId).set(user.toMap(), _options);
}
UserProvider() class:
final userProvider = ChangeNotifierProvider<UserProvider>((ref) {
return;
});
class UserProvider with ChangeNotifier {
final userService = UserService();
String _userId;
String _fullName;
// Getters
String get userId => _userId;
String get fullName => _fullName;
Stream<UserModel> get getCurrentUserData => userService.getCurrentUser();
// Setters
set changeFullName(String fullName) {
_fullName = fullName;
notifyListeners();
}
// Functions
void loadUser(UserModel userModel) {
_userId = userModel.userId;
_fullName = userModel.fullName;
}
void updateUser() {
final _currentUser = UserModel(
userId: _userId,
fullName: _fullName,
);
userService.saveUser(_currentUser);
}
}
EditProfileScreen():
class EditProfileScreen extends StatefulWidget {
const EditProfileScreen({this.userModel});
final UserModel userModel;
#override
_EditProfileScreenState createState() => _EditProfileScreenState();
}
class _EditProfileScreenState extends State<EditProfileScreen> {
final _fullNameController = TextEditingController();
final _validator = Validator();
#override
void dispose() {
_fullNameController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
final userStream = context.read(userProvider);
if (widget.userModel != null) {
// Edit
_fullNameController.text = widget.userModel.fullName;
userStream.loadUser(widget.userModel);
}
}
#override
Widget build(BuildContext context) {
final userData = context.read(userProvider);
return Scaffold(
body: Column(
children: [
TextFormFiled(
hintText: ‘Full name’,
keyboardType: TextInputType.name,
controller: _fullNameController,
validator: (value) => _validator.validateFullName(
value,
),
onChanged: (value) {
userData.changeFullName = value;
debugPrint(value);
}
),
ElevatedButton(
onPressed: () {
userData.updateUser();
Navigator.of(context).pop();
},
child: const Text(‘Save’),
),
],
),
);
}
}
Did you forget to return something?
final userProvider = ChangeNotifierProvider<UserProvider>((ref) {
return; //return a UserProvider()
});

How to write this Flutter code more efficiently?

As you can see in first part I'm checking that a certain value contains in a document from Firestore and returns a boolean value. Now I'm calling that function in a build and based on that return value I'm changing a chip color (second part).
Now the problem is maybe because I'm calling it in a build function so its being called continuously and on that build and it costing me a ton of reads in Firestore or maybe the function is inefficient. How can I write this more efficiently?
checkAtt(String name, id , date) async{
var ref = _db.collection('subjects').document(id).collection('Att').document(date);
var docref = await ref.get();
return docref.data.containsKey(name)
?true
:false;
}
class PresentChip extends StatefulWidget {
final candidate;
PresentChip(
this.candidate, {
Key key,
}) : super(key: key);
#override
_PresentChipState createState() => _PresentChipState();
}
class _PresentChipState extends State<PresentChip> {
var isSelected = false;
var c = false;
#override
Widget build(BuildContext context) {
final SelectSub selectSub = Provider.of<SelectSub>(context);
final Date date = Provider.of<Date>(context);
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});
return Container(
child: ChoiceChip(
label: Text('Present'),
selected: isSelected,
onSelected: (selected) {
db.gibAtt(
widget.candidate, selectSub.selectsub, date.datenew.toString());
setState(() {
isSelected = selected;
});
},
backgroundColor: !c ?Colors.red :Colors.green ,
selectedColor: !c ?Colors.red :Colors.green ,
));
}
}
Assuming you only want to read once from firestore, you need a FutureBuilder.
return Container(
child: FutureBuilder(
future: db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew),
builder: (context, snapshot) {
if(snapshot.hasData)
return ChoiceChip(
...
backgroundColor: !snapshot.data ?Colors.red :Colors.green,
selectedColor: !snapshot.data ?Colors.red :Colors.green,
);
//Return another widget if the future has no data
return Text('Future has no data');
}
)
);
If you need your UI to react to changes from firestore, use a StreamBuilder.
You can remove the following bloc from your build method:
db.checkAtt(widget.candidate, selectSub.selectsub, date.datenew).then((result){
print(result);
setState(() {
c = result;
});
});