Flutter项目中阻拦登录的常用做法
前言
最近也是比较忙着在搞 Flutter 新项目了,在我之前用 Android 开发的思路开发Flutter项目的指导下没什么大问题。
直到最近出现这么一个登陆阻拦的问题。可能许多人没这方面的需求,不是很了解。两个痛点!一个是如何阻拦各种场景与监听的阻拦,一个是如何阻拦结束之后再次履行的问题。
阻拦?简略!这不是用 Flutter 的路由阻拦器就能结束的逻辑吗?
相似假如咱们运用 GetX 框架来办理的路由,大致如下结束:
class AuthMiddleware extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
// 判别用户是否已登录
bool isLoggedIn = checkIfUserIsLoggedIn(); // 你需求依据实际情况来结束该办法
if (!isLoggedIn) {
// 用户未登录,重定向到登录页面
return RouteSettings(name: '/login');
}
// 用户已登录,持续正常跳转
return null;
}
}
然后注入到容器中即可运用,经过以上装备,当进行路由跳转时,AuthMiddleware 中的登录阻拦逻辑会主动触发。假如用户未登录,则会重定向到指定的登录页面;假如用户已登录,则会正常进行路由跳转。
可是这种经过路由阻拦的办法,有很大的局限性,它只能阻拦经过 Get.to、Get.off、Get.toNamed 等 GetX 路由办法进行的页面跳转。当你直接运用结构函数创立的页面时,不会触发 GetX 的路由系统,因此 GetMiddleware 无法阻拦这种情况。
无法了解?
比方主页的底部5个Tab,只要主页能支撑游客形式,而点击其他Tab的时分要阻拦未登录。或许 PageView 滚动的时分只要第一个Page是支撑游客形式,滑动到其他Page 就需求阻拦未登录。再或许在主页的列表 ListView 滚动到必定的阈值需求阻拦未登录。
要知道这些办法的阻拦可并不走路由的。并且就算是正常的按钮点击跳转到路由,走到路由了,运用路由的阻拦办法也只能做到阻拦不能支撑再履行的作用,仍是需求再次点击按钮触发才行。
其中相似的作用如 Rxbool 之类的监听:
class AuthController extends GetxController {
RxBool _isLoggedIn = false.obs;
bool get isLoggedIn => _isLoggedIn.value;
set isLoggedIn(bool value) {
_isLoggedIn.value = value;
}
}
也是相似的作用,确实是能做到监听,可是无法做到再履行的作用。而咱们需求终究结束的作用如下:
那么究竟终究有哪些办法能结束这种作用呢?
一、Future 的等候
其实在上一年我写过 Android 的登录阻拦与转发的几种办法,这儿汲取其中几种思路,咱们就能结束相似的作用。
相似 Kotlin 协程的做法,咱们能够用 Dart 中的 Completer 目标来结束。
Completer 是 Dart 中的一个类,用于处理异步操作的结束告诉。Completer 供给了一个 future 属性,该属性是一个 Future 目标,调用者能够监听该 Future 目标来获取异步操作的成果。
class LoginInterceptThreadManager {
static LoginInterceptThreadManager? _threadManager;
static Completer<bool>? _completer;
LoginInterceptThreadManager._();
static LoginInterceptThreadManager get() {
_threadManager ??= LoginInterceptThreadManager._();
return _threadManager!;
}
void checkLogin(void Function() nextRunnable) {
if (LoginInterceptChain.isLogin()) {
// 现已登录
nextRunnable();
return;
}
// 假如没有登录-先去登录页面
LoginPage.startInstance();
// 等候登录结束
_completer = Completer<bool>();
_completer?.future.then((result) {
if (LoginInterceptChain.isLogin()) {
// 现已登录
nextRunnable();
_completer = null;
}
});
}
// 设置登录结束
void loginFinished() {
_completer?.complete(true);
}
}
在 checkLogin 办法中,首先创立了一个 Completer 目标 _completer,然后将其 future 属性(即 _completer?.future)传递给 then 办法,以增加一个监听器。当 _completer 的 complete 办法被调用时,该监听器会被触发,从而履行相应的逻辑。
在 loginFinished 办法中,调用 _completer?.complete(true) 来结束异步操作,并将成果设置为 true。这将触发之前经过 then 办法增加的监听器,并履行后续的操作。
运用的时分:
比方切换到其他Tab的时分做阻拦
void _selectPositionEvent(int position) {
if (position != state.tabIndex) {
if (position > 0) {
LoginInterceptThreadManager.get().checkLogin(() {
//监听事情,正常切换Tab页面
_pageController.jumpToPage(position);
setState(() {
//状况更新
state.tabIndex = position;
});
});
}
} else {
//假如是重复点击
switch (position) {
case 0:
Log.d('重复点击索引0');
break;
case 1:
Log.d('重复点击索引1');
break;
case 2:
Log.d('重复点击索引2');
break;
case 3:
Log.d('重复点击索引3');
break;
case 4:
Log.d('重复点击索引4');
if (isPageLoadedList[position]) {
Get.find<MeController>().forceRefreshPage();
}
break;
}
}
}
在登录结束之后需求处理登录结束的逻辑。
void doLogin() {
... //调用接口结束
StorageService.getInstance().setString(AppConstant.storageToken,"abc123");
LoginInterceptThreadManager.get().loginFinished();
Get.back();
}
二、新线程 Isolate 的告诉
除了上述的办法,咱们还能模仿结束 Java 的线程等候唤醒的逻辑结束相似的功用,咱们能够用 Dart 开启新的线程 Isolate。然后经过 SendPort 与 receivePort 等结合起来结束。
class LoginInterceptThreadManager2 {
static LoginInterceptThreadManager2? _threadManager;
late StreamSubscription _subscription;
static Isolate? _isolate;
static SendPort? _sendPort;
LoginInterceptThreadManager2._();
static LoginInterceptThreadManager2 get() {
_threadManager ??= LoginInterceptThreadManager2._();
return _threadManager!;
}
void checkLogin(void Function() nextRunnable) {
if (LoginInterceptChain.isLogin()) {
// 现已登录
nextRunnable();
return;
}
// 假如没有登录-先去登录页面
LoginPage.startInstance();
// 发动 Isolate 并等候登录结束
final receivePort = ReceivePort();
Isolate.spawn(_isolateEntry, receivePort.sendPort).then((isolate) {
_isolate = isolate;
_sendPort = receivePort.sendPort;
});
_subscription = receivePort.listen((result) {
if (LoginInterceptChain.isLogin()) {
// 现已登录
_subscription.cancel();
_isolate?.kill(priority: Isolate.immediate);
nextRunnable();
}
});
}
static void _isolateEntry(SendPort sendPort) {
final receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((_) {
if (LoginInterceptChain.isLogin()) {
sendPort.send(true);
}
});
}
// 设置登录结束
static void loginFinished() {
_sendPort?.send(true);
}
}
全体流程是经过发动一个 Isolate 来履行异步操作,并运用 SendPort 和 ReceivePort 进行主 Isolate 与子 Isolate 之间的消息传递,从而结束了异步操作的办理和告诉。
详细的运用与上述代码相似:
void gotoProfilePage() {
LoginInterceptThreadManager2.get().checkLogin(() {
SmartDialog.showToast('现已登录了-去个人中心吧2');
StorageService.getInstance().remove(AppConstant.storageToken);
});
}
登录页面的处理:
void doLogin() {
... //调用接口结束
StorageService.getInstance().setString(AppConstant.storageToken,"abc123");
LoginInterceptThreadManager2.loginFinished();
Get.back();
}
三、阻拦器形式
没想到吧,Android 中常用的阻拦器形式一样能在 Dart/Flutter 中运用。
因为它仅仅一种规划思想,不约束于语言与平台。在 Android 中的运用办法换成 Dart 语法结束之后在 Flutter 中一样好用。
咱们能够界说两个固定的阻拦器,一个是登录阻拦的阻拦器,用于阻拦是否登录并且跳转到登录页面。结束登录之后咱们就放行阻拦器,走到下一个阻拦器是详细的履行操作阻拦器。从而结束登录的阻拦与再履行作用。
由于内部的判别都是基于是否登录来判别的,所以咱们结束一个简略的阻拦器链即可,能够不经过值传递的办法来结束。
先界说阻拦器和基类的结束:
abstract class Interceptor {
void intercept(LoginInterceptChain chain);
}
abstract class BaseLoginInterceptImpl implements Interceptor {
LoginInterceptChain? mChain;
@override
void intercept(LoginInterceptChain chain) {
mChain = chain;
}
}
然后咱们界说两个固定的阻拦器一个是登录阻拦器,一个是持续履行阻拦器:
class LoginInterceptor extends BaseLoginInterceptImpl {
@override
void intercept(LoginInterceptChain chain) {
super.intercept(chain);
if (LoginInterceptChain.isLogin()) {
// 假如现已登录 -> 放行,转交给下一个阻拦器
chain.process();
} else {
// 假如未登录 -> 去登录页面
LoginPage.startInstance();
}
}
void loginFinished() {
// 假如登录结束,调用办法放行到下一个阻拦器
mChain?.process();
}
}
class LoginNextInterceptor implements Interceptor {
final void Function() action;
LoginNextInterceptor(this.action);
@override
void intercept(LoginInterceptChain chain) {
if (LoginInterceptChain.isLogin()) {
// 假如现已登录履行当前的任务
action();
}
chain.process();
}
}
终究咱们经过一个办理类来统一处理:
class LoginInterceptChain {
int index = 0;
List<Interceptor> _interceptors = [];
late LoginInterceptor _loginIntercept;
// 私有结构函数
LoginInterceptChain._() {
// 默许初始化Login的阻拦器
_loginIntercept = LoginInterceptor();
}
// 单例实例
static final LoginInterceptChain _instance = LoginInterceptChain._();
// 获取单例实例
factory LoginInterceptChain() {
return _instance;
}
// 履行阻拦器
void process() {
if (_interceptors.isEmpty) return;
if (index < _interceptors.length) {
Interceptor interceptor = _interceptors[index];
index++;
interceptor.intercept(this);
} else if (index == _interceptors.length) {
clearAllInterceptors();
}
}
// 增加默许的再履行阻拦器
LoginInterceptChain addInterceptor(Interceptor interceptor) {
// 默许增加Login判别的阻拦器
if (!_interceptors.contains(_loginIntercept)) {
_interceptors.add(_loginIntercept);
}
if (!_interceptors.contains(interceptor)) {
_interceptors.add(interceptor);
}
return this;
}
// 放行登录判别阻拦器
void loginFinished() {
if (_interceptors.contains(_loginIntercept) && _interceptors.length > 1) {
_loginIntercept.loginFinished();
}
}
// 清除悉数的阻拦器
void clearAllInterceptors() {
index = 0;
_interceptors.clear();
}
// 是否现已登录
static bool isLogin() {
String token = StorageService.getInstance().getString(AppConstant.storageToken);
return !TextUtil.isEmpty(token);
}
}
运用起来不同其实也不大:
void gotoProfilePage() {
LoginInterceptChain chain = LoginInterceptChain();
chain.addInterceptor(LoginNextInterceptor((){
SmartDialog.showToast('现已登录了-去个人中心吧');
StorageService.getInstance().remove(AppConstant.storageToken);
})).process();
}
登录的处理:
void doLogin() {
... //调用接口结束
StorageService.getInstance().setString(AppConstant.storageToken,"abc123");
LoginInterceptChain().loginFinished();
Get.back();
}
三者结束终究的作用是共同的,这儿就不重复的贴图。
总结
经过上面的几种办法(不限于这些办法还有许多其他办法)咱们就能够脱离框架层面,不需求界说路由阻拦或许每一个当地手写判别。或许一些阻拦不走路由等等各种情况的统一办理。
只需求结束一处代码,能够到达统一阻拦的作用。
以上的这几种办法总的分两种思路,一种经过线程,Future等办法的告诉与回调处理,一种是经过阻拦器的形式来处理。你 pick 哪一种。
iOS搭档觉得阻拦器的办法也太妙了,哎,属实是少见多怪了,Android开发也太卷了其中一些开发规划思路感觉遥遥领先(点个题开个打趣)。拿出一点用在 Flutte 中受用无穷。
所以咱们后边仍是用选用的 Completer 的办法,感觉也蛮轻量挺好用的。
代码比较简略都现已在文中贴出。
假如本文的讲解有什么讹夺的当地,期望同学们必定要指出哦。有疑问也能够谈论区交流学习进步,谢谢!
当然假如觉得本文还不错对你有些协助的话,还请点赞
支撑一下哦,你的支撑是我最大的动力啦!
Ok,这一期就此结束。