I need to get 1 field 1 time from Firebase Cloud Firestore. How do I accomplish this with providers outside of Widget build?
Below are my combined providers. appStartupProvider is a FutureProvider and I want to get the bool value from this 1 field in firestore. However, the await in appStartupProvider states "'await' applied to 'AsyncValue', which is not a 'Future'".
final accountStreamProvider = StreamProvider<Account>((ref) {
final database = ref.watch(databaseProvider);
return database != null ? database.accountStream() : const Stream.empty();
});
final _accountSetupCompleteProvider = Provider<AsyncValue<bool>>((ref) {
return ref
.watch(accountStreamProvider)
.whenData((account) => account?.accountSetupComplete ?? false);
});
final appStartupProvider = FutureProvider<bool>((ref) async {
final accountSetupComplete = await ref.watch(_accountSetupCompleteProvider);
return accountSetupComplete;
});
Obviously missing some key knowledge here on combing providers and AsyncValue, but I'm trying to accomplish the situation stated on RiverPod Combining Providers page, where I see await is being used.
The example in the documentation was incorrect at the time of your post. It has since been updated and is now correct.
This is how you could write it:
final accountStreamProvider = StreamProvider<Account?>((ref) {
final database = ref.watch(databaseProvider);
return database != null ? database.accountStream() : const Stream.empty();
});
final _accountSetupCompleteProvider = FutureProvider<bool>((ref) async {
final account = await ref.watch(accountStreamProvider.last);
return account?.accountSetupComplete ?? false;
});
final appStartupProvider = FutureProvider<bool>((ref) async {
final accountSetupComplete = await ref.watch(_accountSetupCompleteProvider.future);
return accountSetupComplete;
});
Or:
final accountStreamProvider = StreamProvider<Account?>((ref) {
final database = ref.watch(databaseProvider);
return database != null ? database.accountStream() : const Stream.empty();
});
final _accountSetupCompleteProvider = Provider<AsyncValue<bool>>((ref) {
return ref
.watch(accountStreamProvider)
.whenData((account) => account?.accountSetupComplete ?? false);
});
final appStartupProvider = Provider<bool>((ref) {
final accountSetupComplete = ref.watch(_accountSetupCompleteProvider).maybeWhen(
data: (data) => data,
orElse: () => false,
);
return accountSetupComplete;
});
await usage is available via:
.future on FutureProvider documentation
.last on StreamProvider documentation
example
final carsListFutureProvider = FutureProvider<List<Car>>((ref) {
final backend = ref.watch(backendProvider);
return backend.getList(pathName, (json) => Car.fromJson(json));
});
final carFutureProvider = FutureProvider.family<Car?,int>((ref,id) async {
final list = await ref.watch(carsListFutureProvider.future);
return list.firstWhereOrNull((e) => e.id == id);
});
It seems that at the moment the documentation contains incorrect code examples. issue
Related
The problem lies in the FutureBuilder widget section. I am trying to get the data from a snapshot in a FutureBuilder, but I get an error as 'type 'Null' is not a subtype of type 'DatabaseNotes' in type cast'. I tried declaring the _note field as late final but it still throws the same error. I have taken care of the null safety part by adding the ? in after the data type (here, it is DatabaseNotes). I still don't get what is wrong here.
The following is the code for the NewNoteView widget:
class NewNoteView extends StatefulWidget {
const NewNoteView({Key? key}) : super(key: key);
#override
State<NewNoteView> createState() => _NewNoteViewState();
}
class _NewNoteViewState extends State<NewNoteView> {
DatabaseNotes? _note;
late final NotesService _notesService;
late final TextEditingController _textController;
#override
void initState() {
_notesService = NotesService();
_textController = TextEditingController();
super.initState();
}
void _textControllerListener() async {
final note = _note;
if (note == null) {
return;
}
final text = _textController.text;
await _notesService.updateNote(
note: note,
text: text,
);
}
void _setupTextControllerListener() {
_textController.removeListener(_textControllerListener);
_textController.addListener(_textControllerListener);
}
Future<DatabaseNotes> createNewNote() async {
final existingNote = _note;
if (existingNote != null) {
return existingNote;
}
final currentUser = AuthService.firebase().currentUser!;
final email = currentUser.email!;
final owner = await _notesService.getUser(email: email);
return await _notesService.createNote(owner: owner);
}
void _deleteNoteIfTextIsEmpty() {
final note = _note;
if (_textController.text.isEmpty && note != null) {
_notesService.deleteNote(id: note.id);
}
}
void _saveNoteIfTextNotEmpty() async {
final note = _note;
final text = _textController.text;
if (note != null && text.isNotEmpty) {
await _notesService.updateNote(
note: note,
text: text,
);
}
}
#override
void dispose() {
_deleteNoteIfTextIsEmpty();
_saveNoteIfTextNotEmpty();
_textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('New note'),
),
body: FutureBuilder(
future: createNewNote(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
_note = snapshot.data as DatabaseNotes;
_setupTextControllerListener();
return TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: const InputDecoration(
hintText: 'Start typing your note...',
),
);
default:
return const CircularProgressIndicator();
}
},
));
}
}
The following is the code for NotesService:
class DatabaseAlreadyOpenException implements Exception {}
class NotesService {
Database? _db;
List<DatabaseNotes> _notes = [];
static final NotesService _shared = NotesService._sharedInstance();
NotesService._sharedInstance();
factory NotesService() => _shared;
final _notesStreamController =
StreamController<List<DatabaseNotes>>.broadcast();
Stream<List<DatabaseNotes>> get allNotes => _notesStreamController.stream;
Future<DatabaseUser> getOrCreateUser({required String email}) async {
try {
final user = getUser(email: email);
return user;
} on CouldNotFindUser {
final createdUser = createUser(email: email);
return createdUser;
} catch (e) {
rethrow;
}
}
Future<void> _cacheNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
_notesStreamController.add(_notes);
}
Future<DatabaseNotes> updateNote({
required DatabaseNotes note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
// make sure note exists
await getNote(id: note.id);
// update DB
final updatesCount = await db.update(notesTable, {
textColumn: text,
isSyncedWithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}
Future<Iterable<DatabaseNotes>> getAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(
notesTable,
);
return notes.map((noteRow) => DatabaseNotes.fromRow(noteRow));
}
Future<DatabaseNotes> getNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(
notesTable,
limit: 1,
where: 'id = ?',
whereArgs: [id],
);
if (notes.isEmpty) {
throw CouldNotFindNote();
} else {
final note = DatabaseNotes.fromRow(notes.first);
_notes.removeWhere((note) => note.id == id);
_notes.add(note);
_notesStreamController.add(_notes);
return note;
}
}
Future<int> deleteAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final numberOfDeletions = await db.delete(notesTable);
_notes = [];
_notesStreamController.add(_notes);
return numberOfDeletions;
}
Future<void> deleteNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
notesTable,
where: 'id = ?',
whereArgs: [id],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
} else {
_notes.removeWhere((note) => note.id == id);
_notesStreamController.add(_notes);
}
}
Future<DatabaseNotes> createNote({required DatabaseUser owner}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
// make sure the owner exists in the database with the correct id
final dbUser = await getUser(email: owner.email);
if (dbUser != owner) {
throw CouldNotFindUser();
}
const text = '';
// create the note
final noteId = await db.insert(notesTable, {
userIdColumn: owner.id,
textColumn: text,
isSyncedWithCloudColumn: 1,
});
final note = DatabaseNotes(
id: noteId,
userId: owner.id,
text: text,
isSyncedWithCloud: true,
);
_notes.add(note);
_notesStreamController.add(_notes);
return note;
}
Future<DatabaseUser> getUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isEmpty) {
throw CouldNotFindUser();
} else {
return DatabaseUser.fromRow(results.first);
}
}
Future<DatabaseUser> createUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
return DatabaseUser(
id: userId,
email: email,
);
}
Future<void> deleteUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
userTable,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (deletedCount != 1) {
throw CouldNotDeleteUser();
}
}
Database _getDatabaseOrThrow() {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}
Future<void> close() async {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}
Future<void> _ensureDbIsOpen() async {
try {
await open();
} on DatabaseAlreadyOpenException {
//empty block
}
}
Future<void> open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException;
}
try {
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath);
_db = db;
// create the user table
await db.execute(createUserTable);
// create the notes table
await db.execute(createNotesTable);
await _cacheNotes();
} on MissingPlatformDirectoryException {
throw UnableToGetDocumentsDirectory;
}
}
}
#immutable
class DatabaseUser {
final int id;
final String email;
const DatabaseUser({
required this.id,
required this.email,
});
DatabaseUser.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
email = map[emailColumn] as String;
#override
String toString() => 'Person id = $id, email = $email';
#override
bool operator ==(covariant DatabaseUser other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
class DatabaseNotes {
final int id;
final int userId;
final String text;
final bool isSyncedWithCloud;
DatabaseNotes({
required this.id,
required this.userId,
required this.text,
required this.isSyncedWithCloud,
});
DatabaseNotes.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
userId = map[userIdColumn] as int,
text = map[textColumn] as String,
isSyncedWithCloud = (map[isSyncedWithCloudColumn]) == 1 ? true : false;
#override
String toString() =>
'Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text';
#override
bool operator ==(covariant DatabaseNotes other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
You can place the '?' after DatabaseNotes (or DatabaseNote if you are following the FreeCodeCamp tutorial more precisely). Your IDE will probably then tell you the cast to DatabaseNotes is unnecessary. I then removed the 'as DatabaseNotes' entirely and it worked. Final line of code as below.
_note = snapshot.data;
Try this
FutureBuilder<DatabaseNotes>(your code);
I found the mistake and turns out I wasn't handling the null safety after all; I feel like an idiot.
I created an instance of 'DatabaseNotes?' _note, but when I assigned the snapshot's data inside the FutureBuilder in the line _note = snapshot.data as DatabaseNotes, I forgot to add the ? after DatabaseNotes.
The statement should be: _note = snapshot.data as DatabaseNotes?;
Thank you everyone for your answers and suggestions. I highly appreciate your efforts.
After
_note = snapshot.data as DatabaseNotes;
just put
?
This null safety sign. And your code will run errorless. My code so suffers for this damn sign.
I have a Future in my initState function that gets jwt from cache and uses it to get the logged in user's details. The initState function is:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final token = await CacheService().readCache(key: "jwt");
if (token != null) {
await Provider.of<ProfileNotifier>(context, listen: false)
.decodeUserData(
context: context,
token: token,
option: 'home',
);
}
});
}
Now, it does work and I do get the data, but not on the first run. I have to either hot reload the emulator or navigate to another page and come back for the page to rebuild itself and show the data on screen. I don't understand why it doesn't show the data on the first run itself.
ProfileNotifier class:
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
final CacheService _cacheService = CacheService();
ProfileModel _profile = ProfileModel(
profileImage: "",
profileName: "",
profileBio: "",
);
AccountModel _account = AccountModel(
userId: "",
userEmail: "",
userPassword: "",
);
ProfileModel get profile => _profile;
AccountModel get account => _account;
Future decodeUserData({
required BuildContext context,
required String token,
required String option,
}) async {
try {
_profileAPI.decodeUserData(token: token).then((value) async {
final Map<String, dynamic> parsedData = await jsonDecode(value);
var userData = parsedData['data'];
if (userData != null) {
List<String>? userProfileData = await _cacheService.readProfileCache(
key: userData['userData']['id'],
);
if (userProfileData == null) {
final isProfileAvailable =
await Provider.of<ProfileNotifier>(context, listen: false)
.getProfile(
context: context,
userEmail: userData['userData']['userEmail'],
);
if (isProfileAvailable is ProfileModel) {
_profile = isProfileAvailable;
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileImage: '',
profileName: '',
);
}
if (option != 'profileCreation' && isProfileAvailable == false) {
Navigator.of(context).pushReplacementNamed(ProfileCreationRoute);
}
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileName: userProfileData[3],
profileImage: userProfileData[4],
profileBio: userProfileData[5],
);
}
} else {
Navigator.of(context).pushReplacementNamed(AuthRoute);
}
notifyListeners();
});
} catch (e) {
debugPrint('account/profileNotifier decode error: ' + e.toString());
}
}
Future getProfile({
required BuildContext context,
required String userEmail,
}) async {
try {
var getProfileData = await _profileAPI.getProfile(
userEmail: userEmail,
);
final Map<String, dynamic> parsedProfileData =
await jsonDecode(getProfileData);
bool isReceived = parsedProfileData["received"];
dynamic profileData = parsedProfileData["data"];
if (isReceived && profileData != 'Fill some info') {
Map<String, dynamic> data = {
'id': (profileData['account']['id']).toString(),
'userEmail': profileData['account']['userEmail'],
'userPassword': profileData['account']['userPassword'],
'profile': {
'profileName': profileData['profileName'],
'profileImage': profileData['profileImage'],
'profileBio': profileData['profileBio'],
}
};
AccountModel accountModel = AccountModel.fromJson(
map: data,
);
return accountModel;
} else {
return false;
}
} catch (e) {
debugPrint('profileNotifier getProfile error: ' + e.toString());
}
}
Future setProfile({
required String profileName,
required String profileImage,
required String profileBio,
}) async {
_profile.profileName = profileName;
_profile.profileImage = profileImage;
_profile.profileBio = profileBio;
await _cacheService.writeProfileCache(
key: _account.userId,
value: [
_account.userId,
_account.userEmail,
_account.userPassword as String,
profileName,
profileImage,
profileBio,
],
);
notifyListeners();
}
}
CacheService class:
class CacheService {
Future<String?> readCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
String? cache = await sharedPreferences.getString(key);
return cache;
}
Future<List<String>?> readProfileCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
List<String>? cachedData = await sharedPreferences.getStringList(key);
return cachedData;
}
Future writeCache({required String key, required String value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setString(key, value);
}
Future writeProfileCache(
{required String key, required List<String> value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setStringList(key, value);
}
Future deleteCache({
required BuildContext context,
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.remove(key).whenComplete(() {
Navigator.of(context).pushReplacementNamed(AuthRoute);
});
}
}
I can't seem to figure out the problem here. Please help.
EDIT: The data is used to show profileImage of user in CircleAvatar like this:
#override
Widget build(BuildContext context) {
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
drawer: const ProfileDrawer(),
appBar: AppBar(
backgroundColor: Colors.white,
leading: Row(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 9),
child: Builder(builder: (BuildContext context) {
return InkWell(
onTap: () => Scaffold.of(context).openDrawer(),
child: CircleAvatar(
maxRadius: 20.0,
backgroundImage: profile.profileImage.isNotEmpty
? NetworkImage(profile.profileImage)
: null,
child: profile.profileImage.isEmpty
? SvgPicture.asset(
'assets/images/profile-default.svg')
: null),
);
}),
), ....
This CircleAvatar in the appBar shows the image only after the page is rebuilt. There's nothing else on the page except the appbar for now.
When we use ChangeNotifier, it provides two options to access the data. These are:
Read the data - You read the data, it doesn't act as a Stream or State and only one time. This is what you're doing in your case.
Advantage - Whenever the data is needed only one time, for example - Mathematical calculation, you use this.
Disadvantage - It doesn't listen to the changes and the data returned is static.
Watch the data - What you need. It provides the data in a state manner, wherever you access the data using Watch, it (or the widget in the data is used) will be updated whenever the underlying data is updated, even from other Screens/Widgets.
Advantage - The data result is dynamic and the widget is updated whenever the data is updated.
Disadvantage - In case where static data works, it is unnecessary plus it may affect any operations dependent on the data.
There are two ways to use Read and Watch.
The normal functions provided by the Author of the package
//For reading the data
var yourData = Provider.of<YourNotifier>(context, listen: false);
//For watching the data
var yourData = Provider.of<ProfileNotifier>(context, listen: true);
The extension functions on BuildContext provided by the Author:
//For reading the data
var yourData = context.read<YourNotifier>();
//For watching the data
var yourData = context.watch<YourNotifier>();
So, what you need to do is:
Change
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
to
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: true).profile;
//Or
ProfileModel profile = context.watch<ProfileNotifier>().profile;
Edit: Also, considering good UX, you can use a bool flag to update the UI whenever the data is loaded and if it's loading, show a CircularProgressIndicator.
I am integrating the following system into my to-do app:
Every time the user opens the app, it should check whether the date stored in Cloud Firestore has been exceeded.
If this is the case, all To-Dos of the user should be reset to false.
This is the date in Cloud Firestore I’m looking for:
This function should check if the date has been exceeded:
Future<bool> checkTime() async{
DateTime now = DateTime.now();
var query = users.where('Startdatum', isLessThanOrEqualTo: now);
query = query.where('userID', isEqualTo: userID);
final querySnapshot = await query.get();
return querySnapshot.size > 0;
}
And this function should reset all To-Dos to false:
Future allFalse() async{
return await users.doc(userID).get().then((DocumentSnapshot doc) {
var updateMap = new Map();
var toDos = doc['Level'];
for (var item in toDos.keys) {
updateMap[item] = false;
}
doc.reference.update({'Level' : updateMap});
});
}
I created both functions in a separate file (database), as you can see here:
class DatabaseService {
String userID;
DatabaseService(this.userID);
final CollectionReference users =
FirebaseFirestore.instance.collection('users');
Future allFalse() async {
return await users.doc(userID).get().then((DocumentSnapshot doc) {
var updateMap = new Map();
var toDos = doc['Level'];
for (var item in toDos.keys) {
updateMap[item] = false;
}
doc.reference.update({'Level': updateMap});
});
}
Future<bool> checkTime() async {
DateTime now = DateTime.now();
var query = users.where('Startdatum', isLessThanOrEqualTo: now);
query = query.where('userID', isEqualTo: userID);
final querySnapshot = await query.get();
return querySnapshot.size > 0;
}
}
I create an if condition in in inite State that includes checkTime. If checkTime returns true, the Future returns allFalse, which sets all To-Dos to false.
class _UebersichtState extends State<Uebersicht> {
User? user;
late DatabaseService database;
Future<void> connect() async{
final FirebaseAuth auth = FirebaseAuth.instance;
UserCredential result = await auth.signInAnonymously();
user = result.user;
database = DatabaseService(user!.uid);
}
#override
void initState() {
// TODO: implement initState
super.initState();
connect();
Future.delayed(Duration(seconds: 3), () async{
if(await database.checkTime()) {
return await database.allFalse();}
else print('Still time left');
});
}
I used a delay because the connect () function has to be executed first, it initializes database.
When I start the app, no error appears, but the To-Dos are not reset either.
Today we have the 21. 12. 2021 and in Cloud Firestore is 14. 12. 21 deposited.
The function allFalse works, it resets the To-Dos all.
It must be the function checkTime, which does not return a bool in the if condition. I just replaced it with if (0 == 0) and that triggers allFalse.
Can someone help me?
This is just a guess, but I believe this is the problem:
query = query.where('userID', isEqualTo: userID);
The above line would only work if your document had a field userID and said field was equal to your UID, but from what I could gather, you identify the UID by the name of the documents, if that is the case, this should work?
Future<bool> checkTime() async {
CollectionReference users = FirebaseFirestore.instance.collection('users');
final now = DateTime.now();
final doc = await users.doc(userID).get();
final stufenzeit = (doc.data() as Map<String, dynamic>)['Stufenzeit'] as Map<String, dynamic>;
final storedDate = (stufenSetit['Startdatum'] as TimeStamp).toDate();
return now.compareTo(storedDate) > 0;
}
There's probably also a way to do it with queries, but I am not so well versed on those to be completely honest.
I am trying to check if the product already added to the basket and give the message with snackbar "This product already added". So user can not add the same product to the cart again.
here is the code of mine :
//Button calling controller adding item to cart
InkWell(
onTap:(){
_con.addItemToCart(_con.product);
});
//Controller adding item to cart
Future addItemToCart(Product product, {bool reset = false}) async {
setState(() {
this.loadCart = true;
});
Cart value =
await addToCart(product, scaffoldKey, context, quantity: quantity);
setState(() {
this.cart = value;
this.loadCart = false;
this.totalProductsInStock = this.product.availability;
this.totalProductsInStock -= this.totalProductsInStock - this.quantity ;
});
}
Future<Cart> addToCart(Product product, GlobalKey<ScaffoldState> scaffoldKey, BuildContext buildContext, {bool reset = false, int quantity = 1}) async {
var _cartPoduct = new CartProduct();
_cartPoduct.productId = product.id;
_cartPoduct.optionId = product.optionId;
_cartPoduct.quantity = quantity;
Cart value = await CartRepository.addCart(_cartPoduct, reset);
return value;
}
//Request
static Future<Cart> addCart(CartProduct cartProduct, bool reset) async {
User _user = userRepo.currentUser.value;
if (_user.apiToken == null) {
return new Cart();
}
Map<String, dynamic> decodedJSON = {};
cartProduct.userId = _user.id;
final String url =
'${GlobalConfiguration().getString('api_base_url')}';
final client = new http.Client();
final response = await client
.get(url, headers: {HttpHeaders.contentTypeHeader: 'application/json'});
try {
decodedJSON = json.decode(response.body)['data'] as Map<String, dynamic>;
} on FormatException catch (e) {
}
return Cart.fromJSON(decodedJSON);
}
I created a temporary list to check if the product has been added to the cart, and I have it checked with provider. You can check it maybe it will be useful for you
link
I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.