How to conditionally update data in Cloud Firestore? - flutter

I've a setting page in my app that a user can edit to update details. The data screen consists of two Two Textfields and a profile avatar. The app has google(and email&password)as the auth providers. So when the user signs up for the first time I have a method that presents the data to the user.
void readLocal() async {
controllerName = new TextEditingController(text: AuthProvider.of(context).userData.name);
print(controllerName);
controllerEmail = new TextEditingController(text: AuthProvider.of(context).userData.email);
avatarUrl= AuthProvider.of(context).userData.avatarUrl;
// controllerPhoneNumber= new TextEditingController(text: AuthProvider.of(context).userData.phoneNumber);
// Force refresh input
setState(() {});
}
The data is presented as expected. The method is called in
#override
void didChangeDependencies() {
readLocal();
}
To make sure initState has completed. I have an update button that isn't behaving as I would like. When I edit both fields: both of the fields update and the correct writes are sent to Firestore. However if I update one TextField, and press update I lose the data in the other TextField (an empty string is written to Firestore) and vice versa. And if I press the update without editing either fields, both the fields in Firestore end up with empty strings. Here is my update function
void handleUpdateData() {
focusName.unfocus();
focusEmail.unfocus();
setState(() {
isLoading = true;
});
AuthProvider.of(context).userData.docRef.updateData({'name': name, 'email': email,'avatarUrl':avatarUrl}).then((data) async{
print('########################### #############################');
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: "Update success");
}).catchError((err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: err.toString());
});
}
here is a gist
of the dart file. I presume by not editing the fields and then pressing the update button, the update function takes an empty string to update with and write this to Firestore. How do I prevent this?

If you tell Firestore to set a field to an empty string, it will set that field to an empty string. If you don't want Firestore to modify a field, you should not specify that field in the call to updateDate(...).
In practice this means you need to conditionally populate the Map of values, with something like this:
Map<String, dynamic> values = new Map<String,dynamic>();
if (name?.isEmpty ?? true) values["name"] = name;
if (email?.isEmpty ?? true) values["email"] = email;
if (avatarUrl?.isEmpty ?? true) values["avatarUrl"] = avatarUrl;
AuthProvider.of(context).userData.docRef.updateData(values).then((data) async{
...
For the logic of the check for empty strings, see Dart null / false / empty checking: How to write this shorter?

Related

Getting all documents uids from firestore and add them to a list .flutter

I created a new collection named users on my firestore project. Im trying to get a list of all auto generated id's in that collection. So far I tried
late List<String> userID = [];
Future getID() async {
await FirebaseFirestore.instance.collection('users').get().then(
(snapshot) => snapshot.docs.forEach((document) {
userID.add(document.reference.id);
}),
);
}
But whenever I try to access the Strings of id's in the list , it returns an empty list
getID is a future method. It will take some to fetch data. After getting data you need to call setState to update the UI. You dont need to await and .then same time
try
Future getID() async {
FirebaseFirestore.instance.collection('users').get().then((snapshot) {
snapshot.docs.forEach((document) {
userID.add(document.reference.id);
});
setState(() {});
});
}
It would be great to use FutureBuilder for future method.

Added data is only showing after reloading in flutter

here is a popup screen to add the transaction to the app, as you can see here
and when the add button pressed the data will add to database and also to the dislpay , here is the code
ElevatedButton(
//on pressed
onPressed: () async {
final _categoryName = _nameEditingController.text;
if (_categoryName.isEmpty) {
return;
}
final _type = selectedCategoryNotifier.value;
//sending the data to model class
final _category = CategoryModel(
id: DateTime.fromMillisecondsSinceEpoch.toString(),
name: _categoryName,
type: _type,
);
//inserting the data to database
await CategoryDb.instance.insertCategory(_category);
//refreshing the ui
await CategoryDb.instance.refreshUI();
//and quitting the popup screen
Navigator.of(ctx).pop();
},
child: const Text('Add'),
),
and in this code you can see that I called 2 functions that for insert data and also refresh the UI, in the refresh UI function I added the function that to get all data from database to screen, here the code of all functions for CRUD operatins
const databaseName = 'category-database';
abstract class CategoryDbFunctions {
Future<List<CategoryModel>> getCategories();
Future<void> insertCategory(CategoryModel value);
}
//CRUD operations code
class CategoryDb implements CategoryDbFunctions {
CategoryDb._internal();
static CategoryDb instance = CategoryDb._internal();
factory CategoryDb() {
return instance;
}
ValueNotifier<List<CategoryModel>> incomeCategoryListListener =
ValueNotifier([]);
ValueNotifier<List<CategoryModel>> expenseCategoryListListener =
ValueNotifier([]);
#override
Future<void> insertCategory(CategoryModel value) async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
await _categoryDB.add(value);
await refreshUI();
}
#override
Future<List<CategoryModel>> getCategories() async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
return _categoryDB.values.toList();
}
Future<void> refreshUI() async {
final _allCategories = await getCategories();
incomeCategoryListListener.value.clear();
expenseCategoryListListener.value.clear();
await Future.forEach(
_allCategories,
(CategoryModel category) {
if (category.type == CategoryType.income) {
incomeCategoryListListener.value.add(category);
} else {
expenseCategoryListListener.value.add(category);
}
},
);
}
}
so I checked the all things , but I couldn't find where I'm missing parts,
and here is the main part, it is adding to the database also displaying after I refresh the UI or change the tab here you can see what I mean by 'changing the tab'
this is the problem I'm trying to fix this for 2 day, i couldn't find any solution or mistake in my code
There many ways you can handle this problem.
but I dont see where you notify youre ui that the data has been changed, flutter does only update the ui when you use setState etc.. these functions help flutter updating the ui where the data changed.
i would recommend you to use setState in the place you invoke youre dialog.
onTap:(){
setState(){
await dialogStuff();
}
}

Get Shared Preferences Value using Provider State Management

I try to Get Value from SharedPrefereces but I get a null value if I try to get Data in Main Page, I try to get the data when the state on the main page is created but I sometimes get null like this Available URL: http://169.172.70.108:8008/api/v1/iksk/self?idtraining=null
but after hot reload I managed to get the result like this
Available URL: http://169.172.70.208:8008/api/v1/iksk/self?idtraining=2021-01-21
this is my code
#override
void initState() {
// get pelatihan
MySharedPreferences.instance
.getStringValue(key: 'namaPelatihan')
.then((value) {
namaPelatihan = value;
// get nama Peserta
MySharedPreferences.instance
.getStringValue(key: 'namaPeserta')
.then((value) {
namaPeserta = value;
});
});
how do I get real-time results (get results when redirecting to the main page) using provider state management?
first create a function then use async and await with it
then fellow this code
_transitionToNextPageAfterSplash() async {
final auth = await SharedPreferences.getInstance()
.then((value) => value.getBool('auth') ?? false);
}

When we use notifyListeners in ChangeNotifier?

Why we use sometimes notifyListeners and also why we do not use notifyListeners? How can we use a function in changenotifier ?
For instance, in this code, sometimes we used to notifyListeners, but sometimes we did not use notifyListeners (in login() function). Why ? When we use notifyListeners?
String get userEmail => _userEmail;
set userEmail(String value) {
_userEmail = value;
notifyListeners();
}
String get userPassword => _userPassword;
set userPassword(String value) {
_userPassword = value;
notifyListeners();
}
String get userName => _userName;
set userName(String value) {
_userName = value;
notifyListeners();
}
DateTime get dateOfBirth => _dateOfBirth;
set dateOfBirth(DateTime value) {
_dateOfBirth = value;
notifyListeners();
}
Future<bool> login() async {
try {
isLoading = true;
print(userEmail);
print(userPassword);
if (isLogin) {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: userEmail,
password: userPassword,
);
} else {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: userEmail,
password: userPassword,
);
}
isLoading = false;
return true;
} catch (err) {
print(err);
isLoading = false;
return false;
}
}
}
Besides can someone answer me about why we use set method in this code
bool get isLogin => _isLogin;
set isLogin(bool value) {
_isLogin = value;
notifyListeners();
}
The method notifyListeners() is used whenever you want to trigger a rebuild or whenever there is an active listener which is monitoring for the change to perform certain actions
Assume a scenario, You have a data model, and as soon as you fetch the data from APIs you want to parse it and fill in the model class, Also you want that UI will automatically rebuild after getting the data !
For this purpose you can use notifyListeners() in data class and in your UI code, You can wrap the parts which needs to be rebuild with a ChangeNotifierProvider or a Consumer Widget that will monitor for changes and as soon as they encounter any change , They will rebuild the underlying Widget(s).
In the code you shared above, You've used private fields that cannot be accessed outside of this file and you are using getters and setters to basically retrieve them and modify them
Answering your last question now,
We use set method or setter to basically update or modify a value (generally a private value)
Just to provide you some additional info,
You should NOT wrap fields in getters and setters just to be "safe".
This method is Old and not needed in Dart language
You can read more about this here
You can also read about getters and setters in dart here.
Since you are new to StackOverflow, I welcome you.
Happy Fluttering !

Using The Shared Preferences Package for Data Persistance

I am coding a relatively simple app where one can set an emergency contact and in case of emergency, a text is sent to the contact by the touch of a remote button (connected via Bluetooth.) I have used the package contact picker and it works perfectly. Now, the issue is that I'm trying to save the contact locally for when the app is relaunched. The set state line returns an error that I cannot set contact to type string.
final ContactPicker _contactPicker = new ContactPicker();
Contact _contact;
#override
void initState() {
getData();
}
getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_contact = prefs.getString(_contact.toString());
});
}```
maybe u need to decode that string from prefs so that it can get converted in Contact instance.
this line prefs.getString(_contact.toString()) is returning string in $fullName: $phoneNumber this format
e.g.
var decodedList = prefs.getString('contact').split(" ");
setState(() {
_contact = Contact(fullName: decodedList.first, phoneNumber: decodedList[1]);
});