Closed bbrosius closed 3 years ago
Can you show what you were doing before with ref.read
which is the way you should be doing this. ProviderContainer
should not be used like this.
Sure I'll do my best to pull out the relevant sections.
So I have a Service class called authService that is making the API call. So the authService provider is created like this.
final authServiceProvider = ChangeNotifierProvider(
(ref) => AuthService(ref.watch(errorHandlerProvider)));
Declarations of AuthService are as shown
class AuthService with ChangeNotifier {
UserInfo _currentUser;
AuthenticationResult _authenticationResult;
final ErrorHandler errorHandler;
// for R&D purpose
Timer timer;
AuthService(this.errorHandler);
This uses the provider declaration for ErrorHandler in my previous post.
Inside the authService it catches the DioError and calls testError.
} on DioError catch (e) {
print("caught error");
errorHandler.testError();
}
Here is the full error handler class with test error.
final errorHandlerProvider = ChangeNotifierProvider((ref) => ErrorHandler());
final errorProvider = Provider<OneVueError>((ref) {
return ref.watch(errorHandlerProvider).error;
});
class ErrorHandler extends ChangeNotifier {
OneVueError _oneVueError;
OneVueError get error => _oneVueError;
void handleNetworkError(DioError error) {
print(
"Got error : ${error.response?.statusCode} -> ${error.response?.statusMessage}");
final container = ProviderContainer();
if (error.type == DioErrorType.RESPONSE) {
//Redirect to login on unauthorized status
if (error.response?.statusCode == 401) {
container.read(authServiceProvider).deauthenticate();
} else {
_oneVueError = ErrorFactory.errorForNetworkRequest(
error.response.statusCode, error.request.path);
}
} else {
//Log dio error with Crashlytics?
}
notifyListeners();
}
void testError() {
print("Testing error handling");
_oneVueError = OneVueError(ErrorType.unknown, "Test error");
notifyListeners();
}
}
Right now I'm only using the test error method would eventually move to the other method.
From there I would expect my errorProvider to update.
final errorProvider = Provider<OneVueError>((ref) {
return ref.watch(errorHandlerProvider).error;
});
But that never happens. As mentioned before if I either pass through the buildcontext or watch the errorHandlerProvider in the view class and pass it all the way through to the service it works. In that case it would look more like this.
} on DioError catch (e) {
print("caught error");
context.watch(errorHandlerProvider).testError();
}
It updates the errorProvider as expected. But obviously, I would like to avoid passing around the BuildContext which is one of the reasons for using riverpod. Also eventually I would like to hook this up as an interceptor with dio at which point I won't be able to pass in the buildContext since that class isn't directly called by the view.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test(),
);
}
}
class Test extends ConsumerWidget {
const Test();
@override
Widget build(BuildContext context, ScopedReader watch) {
return Scaffold(
body: ProviderListener(
provider: errorProvider,
onChange: (c, err) {
print('Showing snackbar');
ScaffoldMessenger.of(c).showSnackBar(
SnackBar(
content: Text('Error $err'),
),
);
},
child: Center(
child: Column(
children: [
Text(''),
ElevatedButton(
child: Text('Log in'),
onPressed: () {
context.read(authServiceProvider).authenticate();
},
)
],
),
),
),
);
}
}
final authServiceProvider = ChangeNotifierProvider(
(ref) => AuthService(ref.watch(errorHandlerProvider)));
final errorHandlerProvider =
ChangeNotifierProvider((ref) => ErrorHandler(ref.read));
final errorProvider = Provider<OneVueError>((ref) {
return ref.watch(errorHandlerProvider).error;
});
class ErrorHandler extends ChangeNotifier {
Reader read;
ErrorHandler(this.read);
OneVueError _oneVueError;
OneVueError get error => _oneVueError;
void handleNetworkError(DioError error) {
print(
"Got error : ${error.response?.statusCode} -> ${error.response?.statusMessage}");
if (error.type == DioErrorType.RESPONSE) {
//Redirect to login on unauthorized status
if (error.response?.statusCode == 401) {
read(authServiceProvider).deauthenticate();
} else {
_oneVueError = ErrorFactory.errorForNetworkRequest(
error.response.statusCode, error.request.path);
}
} else {
//Log dio error with Crashlytics?
}
notifyListeners();
}
void testError() {
print("Testing error handling");
_oneVueError = OneVueError(ErrorType.unknown, "Test error");
notifyListeners();
}
}
This seems to be working for me. Make sure you pass ref.read
to ErrorHandler, and use that, and not the ProviderContainer
.
ProviderContainer
creates a whole new and different scope and set of states that is separate from the ProviderScope
in the widget tree. Always pass a ref.read
into a Provided object if you anticipate needing to access other providers from it.
Tim's solution is correct, so I will close this.
@TimWhiting Thanks for that I didn't realize the Container was creating a different scope eliminating all usage of that solved the problem. Thanks again.
Describe the bug I'm writing an error handler for rest API requests I'm running into a weird problem with ChangeNotifierProvider. For some reason the notifyListeners() call only seems to work if I make the call with the context.read(). Both ProviderContainer and ref.read don't work.
Here is how I'm declaring my errorHandler providers.
The idea is that my UI code will listen to errorProvider to display error messages after rest calls. And the API side will use errorHandlerProvider to process errors.
I may an API Call like this:
Then the test error message looks like this
Test error is called every time. But my errorProvider break point is never hit, and the UI is never updated. In the code posted above I used a ProviderContainer as a test. I had previously set it up the I think correct way of using ref.read in the provider declaration for the service where the call is made. Neither of those worked. The only time errorProvider is updated is when I hacked up my code to pass the BuildContext from the UI all the way into the service and then called context.read(errorHandlerProvider).testError().
Any ideas how I can get this to work correctly without passing context through into my business logic, or why this is causing a problem?