Riverpod : future provider is stuck on loading - flutter

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);
}

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
}
});
}

Flutter Either fold is skipped

I'm working on a small app with GoogleSignIn-Auth. and stumbled upon a bug I cannot wrap my head around.
It seems like the fold of an Either seems to be skipped. It used to work before, when I had a complicated pile of blocs. Since I started reorganizing my widgets it started to this.
Future<Either<Failure, SignUpSuccess>> signInWithGoogle() async {
try {
final signUpSuccess = await googleRemoteDataSource.signInWithGoogle();
signUpSuccess.fold(
(failure) => () {
print("Got failure!");
return Left(GeneralFailure());
},
(success) => () {
return Right(signUpSuccess);
});
print("I skipped the fold!");
} catch (e) {
print("Caught exception!");
return Left(GeneralFailure());
}
print("Instant fail!");
return Left(GeneralFailure());
}
I have a widget that's listening to a SignInBloc emitting the states:
class SignUpRoot extends StatelessWidget {
SignUpRoot({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => sl<SignInBloc>(),
child: BlocListener<SignInBloc, SignInState>(
listener: (context, state) {
if (state is SignInWithGoogleLoaded) {
// Navigate to SignInNamePage
print("This seems to work!");
} else if (state is SignInWithGoogleLoading) {
// Navigate to loading page
print("Loading Google...");
} else if (state is SignInError) {
// Navigate to error page
print("An error occured while signing in!");
}
},
child: const SignUpMainPage(),
)));
}
And last but not least my bloc:
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final SignUpUseCases useCases;
SignInBloc({required this.useCases}) : super(SignInInitial()) {
on<SignInWithGooglePressed>((event, emit) async {
// Show Loading indicator
emit(SignInWithGoogleLoading());
// wait for sign in response
Either<Failure, SignUpSuccess> successOrFailure =
await useCases.signInWithGoogle();
// emit corresponding state
successOrFailure.fold(
(failure) => emit(SignInError()),
(success) => () {
// emit sign in loaded state
emit(SignInWithGoogleLoaded());
// create new (local) user
// assign user data e.g. display name
});
});
}
}
Thanks for any help!
The problem is that the fold method returns the value of the left or right functions.
https://pub.dev/documentation/dartz/latest/dartz/Either/fold.html
B fold<B>(
B ifLeft(
L l
),
B ifRight(
R r
)
)
Your code should be corrected to:
Future<Either<Failure, SignUpSuccess>> signInWithGoogle() async {
try {
final signUpSuccess = await googleRemoteDataSource.signInWithGoogle();
return signUpSuccess.fold(
(failure) => () {
return Left(GeneralFailure());
},
(success) => () {
return Right(signUpSuccess);
});
} catch (e) {
return Left(GeneralFailure());
}
return Left(GeneralFailure());
}
I just added the return at the start of the fold, now the value returned from left or right will be returned by your function.
You just have to remove the arrow in the success part of the fold.
Future<Either<Failure, SignUpSuccess>> signInWithGoogle() async {
try {
final signUpSuccess = await googleRemoteDataSource.signInWithGoogle();
signUpSuccess.fold(
(failure) => () {
print("Got failure!");
return Left(GeneralFailure());
},
(success){
return Right(signUpSuccess);
});
print("I skipped the fold!");
} catch (e) {
print("Caught exception!");
return Left(GeneralFailure());
}
print("Instant fail!");
return Left(GeneralFailure());
}
class SignInBloc extends Bloc<SignInEvent, SignInState> {
final SignUpUseCases useCases;
SignInBloc({required this.useCases}) : super(SignInInitial()) {
on<SignInWithGooglePressed>((event, emit) async {
// Show Loading indicator
emit(SignInWithGoogleLoading());
// wait for sign in response
Either<Failure, SignUpSuccess> successOrFailure =
await useCases.signInWithGoogle();
// emit corresponding state
successOrFailure.fold(
(failure) => emit(SignInError()),
(success) {
emit(SignInWithGoogleLoaded());
});
});
}
}

why my circularProgressIndicator having strange behavior when async function called?

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();
}
}

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.

How to await a build task in a VS Code extension?

let result = await vscode.commands.executeCommand('workbench.action.tasks.build');
resolves immediately.
How can I await a build task with VS Code API?
I figured it out! Tasks cannot be awaited from vscode.tasks.executeTask, but we can await vscode.tasks.onDidEndTask and check if ended task is our task.
async function executeBuildTask(task: vscode.Task) {
const execution = await vscode.tasks.executeTask(task);
return new Promise<void>(resolve => {
let disposable = vscode.tasks.onDidEndTask(e => {
if (e.execution.task.group === vscode.TaskGroup.Build) {
disposable.dispose();
resolve();
}
});
});
}
async function getBuildTasks() {
return new Promise<vscode.Task[]>(resolve => {
vscode.tasks.fetchTasks().then((tasks) => {
resolve(tasks.filter((task) => task.group === vscode.TaskGroup.Build));
});
});
}
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('extension.helloWorld', async () => {
const buildTasks = await getBuildTasks();
await executeBuildTask(buildTasks[0]);
}));
}
Note that currently there is a bug #96643, which prevents us from doing a comparison of vscode.Task objects: if (e.execution.task === execution.task) { ... }
I think this depends on how the main command is executed in the extension.ts
Being new to JS/TS, I may be wrong here, but just trying to help:
make sure the vscode.command.registerCommand is not asyncronous, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.openSettings', () => {
return vscode.commands.executeCommand("workbench.action.openSettings", "settingsName");
}));
This would be compared to something async, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.removeHost', async (hostID) => {
const bigipHosts: Array<string> | undefined = vscode.workspace.getConfiguration().get('extension.hosts');
const newHosts = Hosts?.filter( item => item != hostID.label)
await vscode.workspace.getConfiguration().update('f5-fast.hosts', newBigipHosts, vscode.ConfigurationTarget.Global);
hostsTreeProvider.refresh();
}));