why my circularProgressIndicator having strange behavior when async function called? - flutter

Im calling a function to get data from Excel file and upload it to my Firestore as following
floatingActionButton: FloatingActionButton(onPressed: () async {
Utils.showLoading(context);
await FireStoreServices.bulkUploadFromExcelToFireStore(
collectionName: 'test',
fileName: 'test',
sheetName: 'test');
Navigator.pop(context);
}),
the problem is my Progress loading indicator not working as expected in this case (not spinning only shows and freeze until the function complete after that its popped)
i tried to replace the awaited function 'bulkUploadFromExcelToFireStore' with Future.delayed and it worked as expected
await Future.delayed(const Duration(seconds: 3), () {});
what might be the problem ?
here is the code of bulkUploadFromExcelToFireStore function
static Future bulkUploadFromExcelToFireStore(
{required String fileName,
required String sheetName,
required String collectionName}) async {
try {
final rowsData = await Utils.readExcelFileData(
excelFilePath: fileName, sheetName: sheetName);
rowsData.removeAt(0);
for (var row in rowsData) {
firebaseFirestore.collection(collectionName).doc(row[0]).set(data, SetOptions(merge: true));
}
} catch (e) {
print('Cached ERROR MESSAGE = = = = ${e.toString()}');
}

I added some validations inside your function to check for possible failures.
It would also be interesting to validate a failure warning and terminate the Progression Indication initialization.
static Future<String> bulkUploadFromExcelToFireStore({required String fileName, required String sheetName,required String collectionName}) async {
try {
final rowsData = await Utils.readExcelFileData(excelFilePath: fileName, sheetName: sheetName);
rowsData.removeAt(0);
if(rowsData.length == 0) {
return "No Items!";
} else {
for (var row in rowsData) {
firebaseFirestore?.collection(collectionName)?.doc(row[0])?.set(data, SetOptions(merge: true));
}
return "Item allocated!";
}
} catch (e) {
return e.toString();
}
}

Related

How to ensure that a future completes and all its sub future calls before continue execution

I have a method that uploads a photo to firebase cloud storage and after that I get the download url for the photo and then update the firebase database document with that url.
My problem here in the ElevatedButton callback when I use uploadProfilePhoto(..).then the code is executed before setPersonalPhotoUrl() method completes its job and set personalPhotoUrl.
I tried to use whenComplete instead but it didn't work. My thought if not mistaken is that uploadProfilePhoto(..).then is completing its future but it does not take into account the completion of that future method setPersonalPhotoUrl(). I need help with this.
fields declared:
UploadTask? uploadTask;
String personalPhotoUrl = '';
the update button:
ElevatedButton(
child: Text('Update Info'),
onPressed: () async {
await uploadProfilePhoto(profilePhotoFile).then((value) async {
// Create an instance of ServiceProvider
final SP = ServiceProvider(
id: currentUserUid!,
name: _controllerName.text.trim(),
last: _controllerLast.text.trim(),
mobile: _controllerMobile.text.trim(),
bio: _controllerBio.text.trim(),
photo: personalPhotoUrl, //problem here the value is ''
serviceId: _selectedServiceId!,
subServices: _selectedSubServicesList,
);
// Create or Update the service provider
try {
await DbServices(uid: currentUserUid!)
.updateSProviderData(SP)
.then((value) async {
// update the customers collection when the future completes.
final customer = Customer(
uid: currentUserUid!,
name: _controllerName.text.trim(),
isServiceProvider: true);
await DbServices(uid: currentUserUid!).updateCustomer(customer);
// update the user displayname in firebaseauth when the future completes.
final user = await FirebaseAuth.instance.currentUser;
if (user != null) {
await user.updateDisplayName(_controllerName.text.trim());
}
});
} catch (e) {
Utils.ShowSnackBar(e.toString());
}
});
Utils.ShowSnackBar('Updated successfully');
Navigator.maybePop(context).then((value) {
if (value == false) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Home(),
));
}
});
})
This is upload photo method which uploads the given photo to FiresStore cloud storage:
Future uploadProfilePhoto(File? photoFile) async {
if (photoFile == null) return;
const path = 'images/profile_photo.jpg';
final storageRef = FirebaseStorage.instance.ref().child(path);
try {
uploadTask = storageRef.putFile(photoFile);
uploadTask?.snapshotEvents.listen((TaskSnapshot taskSnapshot) async {
switch (taskSnapshot.state) {
....
case TaskState.success:
setPersonalPhotoUrl(storageRef);
break;
}
});
} on FirebaseException catch (e) {
// do something
print('ERROR: Exception thrown when uploading the image: $e');
}
}
and this method will be called from within uploadProfilePhoto and set the url:
void setPersonalPhotoUrl(Reference storageRef) async {
personalPhotoUrl = await storageRef.getDownloadURL();
}
I don't won't to update the db document before I make sure that the photo is uploaded and later I want to inform the user that if the photo failed to upload and maybe then set the document field to an empty string
1. Refactor your upload function to.
Future uploadProfilePhoto(
File? photoFile, ValueSetter<TaskSnapshot> resultCallBack) async {
if (photoFile == null) return;
const path = 'images/profile_photo.jpg';
final storageRef = FirebaseStorage.instance.ref().child(path);
try {
UploadTask? uploadTask = storageRef.putFile(photoFile);
uploadTask.snapshotEvents.listen((TaskSnapshot taskSnapshot) async {
resultCallBack(taskSnapshot);
});
} on FirebaseException catch (e) {
// do something
print('ERROR: Exception thrown when uploading the image: $e');
}
}
2. Can then use it like
onPressed: () async {
await uploadProfilePhoto(
profilePhotoFile, (TaskSnapshot taskSnapshotResult) {
// all the results you need are available in taskSnapshotResult
if(taskSnapshotResult.state == TaskState.success){
/// can do what ever you like here
.... // Create an instance of ServiceProvider
final SP = ServiceProvider( ..... blah blah blah
}
});
}

Riverpod : future provider is stuck on loading

Describe the bug
when executing the provider with ref.read or ref.watch the result is the same , it is stuck on the loading block , while testing the api in postman works fine , the funny thing is that the api call gets executed and whenever i print something inside it it appears in the console
To Reproduce
in presentation layer
onpressed:()=>ref
.read(getPatientProvider(
r.api_token))
.when(data: (data) {
data.fold(
(l) => print(
"something wrong happened"),
(r) async {
print(r.id);
print("hello");
patient.value = patient.value
.copyWith(
name: r.name,
aliid: r.id,
appointments: r
.patient_appointments,
fcmtoken: token);
ref.read(docexist(r.id)).when(
loading: () =>
print("loading"),
error: (error, _) =>
print(error),
data: (data) async {
print("heloo");
if (data.isEmpty) {
print(
"data is not empty");
} else {
return print(
"logged in normally");
}
});
});
}, error: (error, _) {
print(error);
}, loading: () {
print("object");
})
Provider with riverpod generator
#riverpod
Future<Either<ApiFailures, dynamic>> getPatient(
GetPatientRef ref, String token) async {
final patientProvider = ref.watch(patientRepositoryProvider);
return patientProvider.getInfo(token);
}
infrastructure layer
#override
Future<Either<ApiFailures, dynamic>> getInfo(String token) {
var dio = Dio();
final result = TaskEither<ApiFailures, PatientModel>(() async {
try {
final response = await dio.get(
"https://xxxxxxxx/GetInfo?api_token=$token");
if (response.data == null) {
return const Left(ApiFailures.notfound());
} else {
PatientModel patientModel =
PatientModel.fromJson(response.data["User"]);
return Right(patientModel);
}
} catch (err, st) {
final message = 'error ${err.runtimeType}]';
if (kDebugMode) log(message, error: err, stackTrace: st);
if (err is DioError) {
return Left(ApiFailures.fromDioError(error: err));
}
return const Left(ApiFailures.internalError());
}
});
return result.map((r) => r).run();
}
Expected behavior
it should get the data as always
Calling when inside a click handler such as onPressed as you did does not make sense.
"when" does not wait for the future to complete. It executes immediately based on the current status of the future.
Considering that when you call it, you just triggered the future, then the future at that time will always be in a loading state.
What you want is something like async/await, where you can wait until the completion of your future.
You could do that with:
onPressed: () async {
final value = await ref.read(provider.future);
}

How to use function output as a conditional of IF ELSE or Ternary operator

So for some background, I implemented a function that reads from Firebase's real-time database and returns a child node. I have built a button that is meant to check if that function returns the object or null if the function returns an object I want the snack bar to display a message.
ElevatedButton(
onPressed: () {
if (validateUsername() != null) {
print("conditional: test");
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
"Username has been taken please try a different one"),
duration: Duration(seconds: 5)));
} else {
return null;
}
},
I had some success with the function by turning it into an async function
validateUsername() async {
final database = FirebaseDatabase.instance.ref().child("/takenUsernames");
await database
.child(_usernameController.text.trim())
.once()
.then((DatabaseEvent event) {
final snapShot = event.snapshot;
final value = snapShot.value;
print("function result: $value");
return value;
});
}
When I turn it to an async function the snack bar displays the message but unfortunately even when the conditional is equal to a null, it for some reason continues to display the message and prints the "test"output. But if I were to try taking away the async the snack bar doesn't print and the "test" in the conditional doesn't print.non-async output
Any help would be appreciated and thanks for your time.
Try this approach, using the await in a variable will wait for the value then the if will evaluate what the result.
ElevatedButton(
onPressed: () async {
String validation = await validateUsername(); // I used type String but you should use the type that will be return.
if (validation != null) {
print("conditional: test");
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
"Username has been taken please try a different one"),
duration: Duration(seconds: 5)));
} else {
return;
}
},
)
add try/catch
validateUsername() async {
try {
final database = FirebaseDatabase.instance.ref().child("/takenUsernames");
await database
.child(_usernameController.text.trim())
.once()
.then((DatabaseEvent event) {
final snapShot = event.snapshot;
final value = snapShot.value;
print("function result: $value");
return value;
});
} catch(e) {
print("err $e");
return null;
}
}
Thanks to some help from #WilsonToribio, I was able to use the information he gave and also implement a few changes to the validateUsername() function
as seen here
validateUsername() async {
try {
final database = FirebaseDatabase.instance.ref().child("/usernames");
final response = await database
.child(_usernameController.text.trim())
.once()
.then((event) {
final dataSnapshot = event.snapshot;
if (dataSnapshot.value != null) {
return dataSnapshot.value;
}
});
return response;
} catch (e) {
print("err $e");
return null;
}
}

How to return catch exception in flutter

I working on error handling of api's. i want if api is crashed then it display a message of "Server is down" something like this, in UI.
I created a class where i'm creating methods of api, here in getBooks method if i modify the api url then it is printing this Exception, and i want it in UI. The problem is getBooks return type is List<Book>> so we can't return this Exception, any solution how to do this?
Exception
E/flutter (12924): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception
here is my api code
class BooksApi {
static Future<List<Book>> getBooks(String query) async {
try {
final url = Uri.parse(
'https://gist.githubusercontent.com/JohannesMilke/d53fbbe9a1b7e7ca2645db13b995dc6f/raw/eace0e20f86cdde3352b2d92f699b6e9dedd8c70/books.json');
final response = await http.get(url);
if (response.statusCode == 200) {
final List books = json.decode(response.body);
return books.map((json) => Book.fromJson(json)).where((book) {
final titleLower = book.title.toLowerCase();
final authorLower = book.author.toLowerCase();
final searchLower = query.toLowerCase();
return titleLower.contains(searchLower) ||
authorLower.contains(searchLower);
}).toList();
} else {
throw Exception;
}
} catch (e) {
print("e");
print(e);
}
throw Exception;
}
}
and calling it like
Future init() async {
setState(() {
isLoading = true;
});
var books = await BooksApi.getBooks(query); //this
var response = await obj.getProduct();
print(response);
setState(() => this.books = books);
setState(() {
isLoading = false;
});
}
You could handle errors with then and onError :
await BooksApi.getBooks(query).then((books) async {
setState(() => {
this.books = books;
this.isLoading = false;
})
}, onError: (error) {
// do something with error
});
or a simple try-catch (you can write try-catch clauses the same way you would in synchronous code).
See handling errors.
You can also use catchError id you don't use async/await :
BooksApi.getBooks(query).then((books) {
setState(() => {
this.books = books;
this.isLoading = false;
})
}).catchError((error, stackTrace) {
print("error is: $error");
});
See futures error handling.
Try to wrap 'var books = await BooksApi.getBooks(query)' with try and catch.
...
try {
var books = await BooksApi.getBooks(query);
} catch (e) {
// To do for UI
}
...
For api, you need to make something like this:
APIModel{
final int code;
// or a success flag
// final bool success;
final String message;
final List<Book> data;
APIModel({this.code,this.message,this.data});
}
It means, every api have its own code,message,and data filed.
When you request, you can check your code or success:
var response = await request(params);
isLoading = false;
if(response.code == 0){}
// or
if(response.success){
// do what you want
}
else {
Toast.show(response.message);
}
You can use build_runner and json_serializable.

type 'Future<List<Appointment>>' is not a subtype of type 'List<Appointment>' in type cast

The error should be clear but I'm unsure how to go around it.
Basically I have a Stream builder I'm calling every second by getData() method to update my SfCalendar with new data.
Stream<DataSource> getData() async* {
await Future.delayed(const Duration(seconds: 1)); //Mock delay
List<Appointment> appointments = foo() as List<Appointment>;
List<CalendarResource> resources = bar() as List<CalendarResource>;
DataSource data = DataSource(appointments, resources);
print("Fetched Data");
yield data;
}
But my appointments method foo() is of type Future<List> and not List.
Future<List<Appointment>> foo() async {
var url0 = Uri.https(
"uri",
"/profiles.json");
List<Appointment> appointments = [];
try {
final response = await dio.get(url0.toString());
//final Random random = Random();
//_colorCollection[random.nextInt(9)];
response.data.forEach((key, value) {
appointments.add(
Appointment(
id: int.parse(
value["id"],
),
startTime: DateTime.parse(value["startTime"]),
endTime: DateTime.parse(value["endTime"]),
),
);
});
} catch (error) {
print(error);
}
return appointments;
}
That is what the error should be telling, yes?
I tried removing the Future cast from foo() appointments but then I can't use async.
I also tried returning Future.value(appointments) but same error.
This is where I call my Stream in initState():
#override
void initState() {
super.initState();
print("Creating a sample stream...");
Stream<DataSource> stream = getData();
print("Created the stream");
stream.listen((data) {
print("DataReceived");
}, onDone: () {
print("Task Done");
}, onError: (error) {
print(error);
});
print("code controller is here");
}
Thank you, please help when possible
Just like JavaScript, async functions always return a Future. That's why you can't use async when you remove Future from the return type.
Since you're not waiting for that Future to resolve, you're actually trying to cast a Future to a List, which isn't a valid cast. All you should need to do is wait for the function to finish so it resolves to a List:
List<Appointment> appointments = await foo() as List<Appointment>;
and, since your return type is Future<List<Appointment>>, you don't actually need to cast the result.
List<Appointment> appointments = await foo();