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 滚动到必定的阈值需求阻拦未登录。

遥遥领先?Flutter项目中如何实现拦截登录再执行

要知道这些办法的阻拦可并不走路由的。并且就算是正常的按钮点击跳转到路由,走到路由了,运用路由的阻拦办法也只能做到阻拦不能支撑再履行的作用,仍是需求再次点击按钮触发才行。

其中相似的作用如 Rxbool 之类的监听:

class AuthController extends GetxController {
  RxBool _isLoggedIn = false.obs;
  bool get isLoggedIn => _isLoggedIn.value;
  set isLoggedIn(bool value) {
    _isLoggedIn.value = value;
  }
}

也是相似的作用,确实是能做到监听,可是无法做到再履行的作用。而咱们需求终究结束的作用如下:

遥遥领先?Flutter项目中如何实现拦截登录再执行

那么究竟终究有哪些办法能结束这种作用呢?

一、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,这一期就此结束。

遥遥领先?Flutter项目中如何实现拦截登录再执行