- 如下配置后,即可实现路由懒加载
如未实现懒加载:请检查RoutePath中是否定义了static const String XXX = ‘/’
原因:MaterialApp设置了路由后,会先访问一次’/‘地址,再访问initialRoute设置的路由。
我之前设置了static const String tab = ‘/’,但是tab中会从服务器拿数据,又做了登录校验,导致每次启动都被拦截。
所以做了登录校验后,’/'只能为登录页或不被拦截的页面。
入口文件
日志:pretty_dio_logger: ^1.4.0
build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: RouteUtils.navigatorKey,
// 懒加载,动态路由,按需加载
onGenerateRoute: Routes.generateRoute,
// 初始页面
initialRoute: RoutePath.launch,
);
}
Widget
路由管理类
// 路由管理类
class Routes {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case RoutePath.launch:
return pageRoute(const LaunchPage(), settings: settings);
case RoutePath.login:
return pageRoute(const LoginPage(), settings: settings);
case RoutePath.tab:
return pageRoute(const TabPage(), settings: settings);
}
return MaterialPageRoute(
builder: (context) => Scaffold(
body:
Center(child: Text('No route defined for ${settings.name}'))));
}
static MaterialPageRoute pageRoute(
Widget page, {
RouteSettings? settings,
bool? fullscreenDialog,
bool? maintainState,
bool? allowSnapshotting,
}) {
return MaterialPageRoute(
builder: (context) => page,
settings: settings,
fullscreenDialog: fullscreenDialog ?? false,
maintainState: maintainState ?? true,
allowSnapshotting: allowSnapshotting ?? true);
}
}
/// 路由路径
class RoutePath {
/// 启动页
static const String launch = '/launch';
/// 首页
static const String tab = '/tab';
/// 登录
static const String login = '/login';
}
路由工具类
/// 路由工具类
class RouteUtils {
RouteUtils._();
static final navigatorKey = GlobalKey<NavigatorState>();
// App 根节点Context
static BuildContext get context => navigatorKey.currentContext!;
static NavigatorState get navigator => navigatorKey.currentState!;
/// 普通动态跳转-->page
static Future push(
BuildContext context,
Widget page, {
bool? fullscreenDialog,
RouteSettings? settings,
bool maintainState = true,
}) {
return Navigator.push(
context,
MaterialPageRoute(
builder: (_) => page,
fullscreenDialog: fullscreenDialog ?? false,
settings: settings,
maintainState: maintainState,
));
}
/// 根据路由路径跳转
static Future pushForNamed(
BuildContext context,
String name, {
Object? arguments,
}) {
return Navigator.pushNamed(context, name, arguments: arguments);
}
/// 自定义route动态跳转
static Future pushForPageRoute(BuildContext context, Route route) {
return Navigator.push(context, route);
}
/// 清空栈,只留目标页面
static Future pushNamedAndRemoveUntil(
BuildContext context,
String name, {
Object? arguments,
}) {
return Navigator.pushNamedAndRemoveUntil(context, name, (route) => false,
arguments: arguments);
}
/// 清空栈,只留目标页面
static Future pushAndRemoveUntil(
BuildContext context,
Widget page, {
bool? fullscreenDialog,
RouteSettings? settings,
bool maintainState = true,
}) {
return Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => page,
fullscreenDialog: fullscreenDialog ?? false,
settings: settings,
maintainState: maintainState,
),
(route) => false);
}
/// 用新的路由替换当路由
static Future pushReplacement(BuildContext context, Route route,
{Object? result}) {
return Navigator.pushReplacement(context, route, result: result);
}
/// 用新的路由替换当路由
static Future pushReplacementNamed(
BuildContext context,
String name, {
Object? result,
Object? arguments,
}) {
return Navigator.pushReplacementNamed(context, name,
arguments: arguments, result: result);
}
/// 关闭当前页面
static void pop(BuildContext context) {
Navigator.pop(context);
}
/// 关闭当前页面:包含返回值
static void popOfData<T extends Object?>(BuildContext context, {T? data}) {
Navigator.of(context).pop(data);
}
}
DIO拦截器
DIO:dio: ^5.7.0
/// 自定义拦截器
class CustomInterceptors extends Interceptor {
/// 请求拦截
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// http header 头加入 Authorization
// final token = tokenStorage.getAccessToken() as String;
// options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
}
/// 响应拦截
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 200 请求成功
if (response.statusCode != 200 || response.data['code'] != 200) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
),
true,
);
} else {
// 截取response.data
response.data = response.data['data'];
handler.next(response);
}
}
// // 退出并重新登录
// Future<void> _errorNoAuthLogout() async {
// await UserService.to.logout();
// IMService.to.logout();
// Get.toNamed(RouteNames.systemLogin);
// }
/// 错误拦截
Future<void> onError(
DioException err, ErrorInterceptorHandler handler) async {
final exception = HttpException(err.message ?? "error message");
switch (err.type) {
case DioExceptionType.badResponse: // 服务端自定义错误体处理
{
final response = err.response;
final errorMessage = ErrorMessageModel.fromJson(response?.data);
final code = response!.data['code'];
switch (code) {
// 401 未登录
case 401:
consoleLog(msg: errorMessage.message ?? '401 未登录');
// 刷新令牌校验
// _refreshToken(err, handler);
// 注销 并跳转到登录页面
PPC.toLaunch();
// _errorNoAuthLogout();
break;
case 404:
break;
case 500:
break;
case 502:
break;
default:
break;
}
// 显示错误信息
// if(errorMessage.message != null){
// Loading.error(errorMessage.message);
// }
}
break;
case DioExceptionType.unknown:
consoleLog(msg: '未知错误');
break;
case DioExceptionType.cancel:
consoleLog(msg: '取消请求');
break;
case DioExceptionType.connectionTimeout:
consoleLog(msg: '连接超时');
break;
default:
break;
}
DioException errNext = err.copyWith(
error: exception,
);
handler.next(errNext);
}
void _refreshToken(DioException err, ErrorInterceptorHandler handler) async {
// 创建新Dio实例,避免循环引用
final dio = Dio();
// 刷新令牌地址
const path = BASE_URL + REFRESH_TOKEN_URL;
try {
String refreshToken = await tokenStorage.getRefreshToken();
final resp = await dio.post(
path,
data: {'refreshToken': refreshToken},
);
consoleLog(msg: '刷新令牌');
// 更新访问令牌和刷新令牌
final accessToken = resp.data['accessToken'] as String;
tokenStorage.setToken(
resp.data['accessToken'], resp.data['refreshToken']);
// 重试原请求
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer $accessToken';
final cloneReq = await dio.fetch(opts);
return handler.resolve(cloneReq);
} catch (e) {
consoleLog(msg: '刷新令牌出错!');
consoleLog(msg: e.toString());
}
}
/// 日志打印
void consoleLog({String msg = ''}) {
log('dio_interceptors -- $msg');
}
}
Token管理类
三方库:flutter_secure_storage: ^9.2.2
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// Token管理类
class TokenStorage {
static final TokenStorage instance = TokenStorage.init();
factory TokenStorage() {
return instance;
}
TokenStorage.init();
static const _instance = FlutterSecureStorage();
Future<void> setAccessToken(String accessToken) async {
await _instance.write(key: 'accessToken', value: accessToken);
}
Future<void> setToken(String accessToken, String refreshToken) async {
await _instance.write(key: 'accessToken', value: accessToken);
await _instance.write(key: 'refreshToken', value: refreshToken);
}
Future<String> getAccessToken() async {
String token = await _instance.read(key: 'accessToken') ?? '';
return token;
}
Future<String> getRefreshToken() async {
String token = await _instance.read(key: 'refreshToken') ?? '';
return token;
}
Future<void> deleteAllTokens() async {
await _instance.deleteAll();
}
}