'Flutter provide: Change state
I don't know how to title this question.
Using Flutter with provide, I have an AuthState class and an AuthService class.
AuthState is a blown up simple class with public members turned private and decorated with getters and setters because of notifyListeners()
import 'dart:developer';
import 'package:flutter/foundation.dart';
class AuthState extends ChangeNotifier {
bool _isBusy = false;
String? _codeVerifier;
String? _authorizationCode;
String? _refreshToken;
String? _accessToken;
String? _idToken;
String? _userInfo;
bool get isBusy => _isBusy;
set isBusy(bool value) {
log('isBusy: $value');
_isBusy = value;
notifyListeners();
}
String? get codeVerifier => _codeVerifier;
set codeVerifier(String? value) {
log('codeVerifier: $value');
_codeVerifier = value;
notifyListeners();
}
String? get userInfo => _userInfo;
set userInfo(String? value) {
log('userInfo: $value');
_userInfo = value;
notifyListeners();
}
String? get idToken => _idToken;
set idToken(String? value) {
log("idToken: $value");
_idToken = value;
notifyListeners();
}
String? get accessToken => _accessToken;
set accessToken(String? value) {
log('accessToken: $value');
_accessToken = value;
notifyListeners();
}
String? get refreshToken => _refreshToken;
set refreshToken(String? value) {
log('refreshToken: $value');
_refreshToken = value;
notifyListeners();
}
String? get authorizationCode => _authorizationCode;
set authorizationCode(String? value) {
log('authorizationCode: $value');
_authorizationCode = value;
notifyListeners();
}
}
AuthService is the class that does all the funky chatting with AppAuth, it's more or less a copy of the AppAuth example.
import '../state/authstate.dart';
class AuthService {
// access AuthState here
// ... various member functions dealing with auth writing to AuthState members, but here's an example
Future<void> signInWithAutoCodeExchange(
{bool preferEphemeralSession = false}) async {
try {
setBusyState();
// show that we can also explicitly specify the endpoints rather than getting from the details from the discovery document
final AuthorizationTokenResponse? result =
await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
clientId,
redirectUrl,
serviceConfiguration: serviceConfiguration,
scopes: scopes,
preferEphemeralSession: preferEphemeralSession,
),
);
// this code block demonstrates passing in values for the prompt parameter. in this case it prompts the user login even if they have already signed in. the list of supported values depends on the identity provider
// final AuthorizationTokenResponse result = await _appAuth.authorizeAndExchangeCode(
// AuthorizationTokenRequest(_clientId, _redirectUrl,
// serviceConfiguration: _serviceConfiguration,
// scopes: _scopes,
// promptValues: ['login']),
// );
if (result != null) {
processAuthTokenResponse(result);
await testApi(result);
}
} catch (_) {
clearBusyState();
}
}
void processAuthTokenResponse(AuthorizationTokenResponse response) {
authState.accessToken = response.accessToken!;
authState.idToken = response.idToken!;
authState.refreshToken = response.refreshToken!;
}
}
What is the proper way to reference AuthState so that a screen in an app that is a child of Multiprovider gets updated when an AuthState member variable is changed from AuthService?
As you see above in processAuthTokenResponse 3 member variables are changed, but this change doesn't reflect in the Widget tree.
ONLY when I move all those AuthService methods inside AuthState essentially bloating it the Widgets get updated.
MyApp
import 'package:blogs/common/theme.dart';
import 'package:blogs/screen/home.dart';
import 'package:blogs/state/authstate.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => AuthState())],
child: MaterialApp(
title: 'Blogs',
theme: appTheme,
initialRoute: '/',
routes: {'/': (context) => Home()},
),
);
}
}
Home
import 'dart:io' show Platform;
import 'package:blogs/state/authstate.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var authState = context.watch<AuthState>();
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Visibility(
visible: authState.isBusy,
child: const LinearProgressIndicator(),
),
ElevatedButton(
child: const Text('Sign in with no code exchange'),
onPressed: authState.signInWithNoCodeExchange,
),
ElevatedButton(
child: const Text('Exchange code'),
onPressed: authState.authorizationCode != null ? authState.exchangeCode : null,
),
ElevatedButton(
child: const Text('Sign in with auto code exchange'),
onPressed: () => authState.signInWithAutoCodeExchange(),
),
if (Platform.isIOS)
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
child: const Text(
'Sign in with auto code exchange using ephemeral session (iOS only)',
textAlign: TextAlign.center,
),
onPressed: () => authState.signInWithAutoCodeExchange(
preferEphemeralSession: true),
),
),
ElevatedButton(
child: const Text('Refresh token'),
onPressed: authState.refreshToken != null ? authState.refresh : null,
),
ElevatedButton(
child: const Text('End session'),
onPressed: authState.idToken != null
? () async {
await authState.endSession();
}
: null,
),
const Text('authorization code'),
Text(authState.authorizationCode.toString()),
const Text('access token'),
Text(authState.accessToken.toString()),
const Text('id token'),
Consumer(builder: (context, state, child) {
return Text('${authState.idToken}');
}),
Text(authState.idToken.toString()),
const Text('refresh token'),
Text(authState.refreshToken.toString()),
const Text('test api results'),
Text(authState.userInfo ?? ''),
],
),
),
),
);
}
}
I can't use var authState = context.watch<AuthState>(); in AuthService because there is no context, it's not a Widget, nor do I want it to be one.
Why I ask is I'm worried about memory consumption, I'd rather have a slim state class that is watchable than a bloated state class, if that makes sense.
I'm guessing provider is pushing me towards not shooting myself in the foot by not propagating outside state changes, but I like separation of concerns and even if AuthService and AuthState is the same concern, fine grained state and service are 2 pairs of shoes and I just feel like they don't belong in the same class that is passed around essentially all Widgets since the auth state is interesting to every screen.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
