Provider was disposed before a value was emitted - flutter

I wrote the following code and encountered the error The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
I thought it was strange, so I debugged FutureProvider's onDispose and found that it was disposed during the await of the API call, which is confirmed by the output of disposed!
class HogeNotifier extends StateNotifier<Hoge> {
onFormSubmitted(String input) async {
final value = await _reader(searchProvider(input).future); // The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
// execute by using value
}
}
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.onDispose(() {
print("disposed!");
});
try {
final result = await dataSource.find(keyword: value); //call api asynchronously
return Future.value(result);
} on Exception catch (e) {
return Future<Data>.error(e);
}
});
Why does the above error occur? How can I solve this problem?

maybe this can help solve the problem:
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.keepAlive();
/// do next
});

Related

How can I set the order of execution for different future in Flutter?

I'm implementing a flutter project using Getx library.
My current project has a format in which the response from the backend is recombined at the front end to the ui model and show on the screen. (Because the project's d-day is very close, so I could not change backend. I should just use a existing backend response).
Meanwhile, I was writing a code that some api call futures must keep the order.
To put it briefly, I receive a survey's result list and make a string list with survey's id (it is in the survey's result object).
After that, I receive a survey list and compare it to survey's result id list.
Through these courses, I will be able to know whether the survey has been completed or not from the survey list.
To implement this action, I declared two future, and I thought that the future was guaranteed the order by executing it with wait.
But nothing is changed in my view...
Below things are my codes.
class ExampleController extends GetxController {
final PreferenceManager _preferenceManager =
Get.find(tag: (PreferenceManager).toString());
/// ------------> related to survey values
final RxList<SurveyListUiModel> _rxSurveyListUiModelList = RxList.empty();
List<SurveyListUiModel> get surveyListUiModelList =>
_rxSurveyListUiModelList.toList();
final List<String> _surveyResultIdList = [];
void getSurveyConfigListWithSurveyResult() async{
Future<String> surveyResultListFuture =
_preferenceManager.getString('survey_result_list');
callDataService(
surveyResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
Future<String> surveyListFuture =
_preferenceManager.getString('survey_list');
await callDataService(
surveyConfigListFuture,
onSuccess: _handleSurveyListResponseSuccess,
);
setListLoading(false);
}
void _handleSurveyResultListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse = SurveyConfigResponse.fromJson(
element['survey_config'] as Map<String, dynamic>);
_surveyResultIdList.add(surveyConfigResponse.id);
}
}
void _handleSurveyListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
surveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
_rxSurveyListUiModelList.refresh();
}
}
/// ------------> related to survey values
#override
void onInit() {
getSurveyConfigListWithSurveyResult();
super.onInit();
}
}
// callDataService method
dynamic callDataService<T>(
Future<T> future, {
Function(Exception exception)? onError,
Function(T response)? onSuccess,
Function? onStart,
Function? onComplete,
}) async {
Exception? _exception;
onStart == null ? showLoading() : onStart();
try {
final T response = await future;
if (onSuccess != null) onSuccess(response);
onComplete == null ? hideLoading() : onComplete();
return response;
} on ServiceUnavailableException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on UnauthorizedException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on TimeoutException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on NetworkException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on JsonFormatException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on NotFoundException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} on ApiException catch (exception) {
_exception = exception;
} on AppException catch (exception) {
_exception = exception;
showErrorMessage(exception.message);
} catch (error) {
_exception = AppException(message: "$error");
logger.e("Controller>>>>>> error $error");
}
if (onError != null) onError(_exception);
onComplete == null ? hideLoading() : onComplete();
}
// example view
class ExampleView extends GetView<ExampleController> {
#override
Widget body(BuildContext context) {
return Obx(() => Text(controller.surveyListUiModelList.length.toString()));
}
}
What is the missing point in my codes..?
Edit 1
With obove code, the lists in the _handle~~~Success method has a right value.
A bit tricky to follow your code, but I think you should be awaiting the first call to callDataService(...)
I.e. this:
callDataService(
surveyLifeResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
should be:
await callDataService(
surveyLifeResultListFuture,
onSuccess: _handleSurveyResultListResponseSuccess,
);
Oh It's my mistake. The method callDataService is an already synchronized method...
So I did not have to consider asynchronization.
The order in which the code was written was applied in the order of execution.
A real problem was RxList.
In my question code, I writed a getter method of RxList like this.
List<SurveyListUiModel> get surveyListUiModelList => _rxSurveyListUiModelList.toList();
The toList() method is just extracting a Growable List from RxList.
it's code is here.
List<E> toList({bool growable = true}) {
if (this.isEmpty) return List<E>.empty(growable: growable);
var first = this[0];
var result = List<E>.filled(this.length, first, growable: growable);
for (int i = 1; i < this.length; i++) {
result[i] = this[i];
}
return result;
}
And, with that extracted list(copy the past state of RxList), I tried adding new items...
So, the obx widget in exampleView that is observing RxList did not response the past extracted list.
To solve this question, I changed my add items code with RxList keeping others in same.
before
void _handleSurveyConfigListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
surveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
}
_rxSurveyListUiModelList.refresh();
}
after
void _handleSurveyConfigListResponseSuccess(String response) {
List<dynamic> list = jsonDecode(response) as List<dynamic>;
for (var element in list) {
SurveyConfigResponse surveyConfigResponse =
SurveyConfigResponse.fromJson(element as Map<String, dynamic>);
_rxSurveyListUiModelList.add(SurveyListUiModel(
surveyConfigId: surveyConfigResponse.id,
surveyConfigTitle: surveyConfigResponse.title,
isDiagnosed: _surveyResultIdList.contains(surveyConfigResponse.id),
));
}
_rxSurveyListUiModelList.refresh();
}

ReadToken() is returning "Instance of 'Future<Object>'"

ReadToken() is returning "Instance of 'Future'"
I was following this tutorial on the Flutter Docs: https://docs.flutter.dev/cookbook/persistence/reading-writing-files.
So, my problem is that, if I just run ReadToken() without running the create function then the ReadToken() function always returns "Instance of 'Future'". Note: I made some changes to the ReadToken() function, like the name. The function is below.
Future<Object> readToken() async {
try {
final file = await _localFile;
// Read the file
final contents = await file.readAsString();
return contents;
} catch (e) {
// If encountering an error, return 0
return 0;
}
}
}
Is there anything that I'm doing wrong or anything that I should change?
You have to await readToken(). If you continue reading the documentation by the complete example section, it shows this example:
#override
void initState() {
super.initState();
widget.storage.readCounter().then((value) {
setState(() {
_counter = value;
});
});
}
It's using .then() instead of await, which await is a syntactic sugar for .then()
So, In your case it would be:
readToken().then((value) {
// Do something with the `value`
});

How to throw error inside riverpod future provider and catch it on error flutter

final loginProvider =
FutureProvider.family<bool, LoginParam>((ref, param) async {
if (param.sgId == '' || param.password == '') {
return false;
}
final http.Response response =
await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity =
await ref.watch(userDefaultsProvider(param.sgId).future);
//ref.state = AsyncValue.data(true);
return true;
} else {
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
});
void login(String userName, String password) async {
state = AsyncValue.loading();
AsyncValue<bool> result;
try {
result = await ref.refresh(loginProvider(LoginParam(userName, password)));
state = result;
} catch (e) {
state = AsyncError(e);
}
}
I'm trying to throw an custom exception inside riverpod future provider and catch the exception in other state notifier classes, but the catch block is not triggered.
Is there any other way to handle exceptions that future provider throw.
First of all, you won't have to manually catch errors inside a FutureProvider, it will do that for you. Refer this example.
Generally, the operations that happen after certain "user interaction" like a button click (in this case, login operation), are not meant to be written in FutureProvider. Scenarios where you'd be using FutureProvider are as follows:
Fetching some data over HTTP/HTTPS.
Performing operations like reading a file or a local database.
So your use case of login can be achieved using a StateNotifier.
// auth_provider.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
// Always prefer some strongly typed object to
// know current status of authentication.
enum AuthState {
unauthenticated,
authenticated,
authenticating,
failed,
}
// StateNotifier is recommended to encapsulate all your business
// logic into a single class and use it from there.
class AuthStateNotifier extends StateNotifier<AuthState> {
// Initialize with the default state of "unauthenticated".
const AuthStateNotifier() : super(AuthState.unauthenticated);
Future<void> login(LoginParam params) async {
if (param.sgId.isEmpty || param.password.isEmpty) {
state = AuthState.failed;
return;
}
final http.Response response = await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity = await ref.watch(userDefaultsProvider(param.sgId).future);
state = AuthState.authenticated;
return;
} else {
state = AuthState.failed;
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
}
}
// Finally, create a provider that can be consumed in the presentation layer (UI).
final authProvider = StateNotifierProvider<AuthStateNotifier, AuthState>((ref) => const AuthStateNotifier());
Then, in your UI part, usually in the onTap / onPressed event handler of button, you can use it as follows. Please note that, we have created a button widget that extends the ConsumerWidget to access the ref.
// login.dart
import 'auth_provider.dart';
class LoginButton extends ConsumerWidget {
final LoginParam params;
const LoginButton({
Key? key,
required this.params,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
void login() {
try {
await ref.read(authProvider.notifier).login(params);
} catch (e) {
// Handle error here.
}
}
return ElevatedButton(
child: Text('Login'),
// Call the handler here.
onPressed: login,
);
}
}

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

Riverpod giving a bad state exception when one hits back button on webpage

I'm getting this error in my StateNotifiers when one hits the back button on their webpage. I've isolated it to happening where the longRunningAPI request is below.
Exception has occurred.
"Error: Bad state: Tried to use RunListNotifier after `dispose` was called.
and I have code like this.
final runListController = StateNotifierProvider.autoDispose
.family<RunListNotifier, AsyncValue<List<Run>>, RunListParameter>(
(ref, param) {
return RunListNotifier(read: ref.read, param: param);
});
class RunListNotifier extends StateNotifier<AsyncValue<List<Run>>> {
RunListNotifier({required this.read, required this.param})
: super(AsyncLoading()) {
fetchViaAPI(param);
}
final Reader read;
final RunListParameter param;
void fetchViaAPI(RunListParameter param) async {
state = AsyncLoading();
try {
List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
state = AsyncData(stuff);
} catch (e) {
state = AsyncError(e);
}
}
}
is it safe to simply do something like this in the catch?
} catch (e) {
if (e.runtimeType.toString() == 'StateError') {
// ignore the error
} else {
state = AsyncError(e);
}
}
I believe you could solve this problem by checking mounted before setting the state after your API call like so:
List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
if (!mounted) return;
state = AsyncData(stuff);
This simply checks if dispose was called and if so, don't attempt to modify the state.
Another resource that could be useful is adding a cancelToken to your API call and canceling if the provider is disposed.
final longRunningApi = FutureProvider.autoDispose.family<List<Run>, RunListParameter>((ref, param) async {
final cancelToken = CancelToken();
ref.onDispose(cancelToken.cancel);
final api = await ref.watch(apiProvider);
final res = await api.longRunningApi(param, cancelToken);
ref.maintainState = true;
return res;
});
Then you'd have to add the cancelToken to your actual request. A great example of this in the marvel example project by the author of Riverpod can be found here.