当前位置:网站首页>FlutterWeb瀏覽器刷新後無法回退的解决方案

FlutterWeb瀏覽器刷新後無法回退的解决方案

2022-07-06 17:40:00 代碼不難寫

問題

在Flutter開發的網頁運行時,瀏覽器刷新網頁後,雖然會顯示刷新前的頁面(前提是用靜態路由跳轉),但這時調用Navigator.pop方法是回不到上一頁的,包括點擊瀏覽器的回退按鈕也是無效的(地址欄中的url會變,頁面不會變)。

原因

當瀏覽器刷新時,Flutter引擎會重新啟動,並加載當前頁面,也就是說,刷新後的Flutter內存中所有靜態變量都被初始化,頁面棧內之前的頁面記錄都未保留,只有當前的頁面。就像是瀏覽網頁時,把其中一頁的網址拷出來,在新的標簽頁再次打開。

解决方案

(一)思路

知道什麼原因引起的,就針對性解决。頁面棧記錄丟失,那麼就代碼中自己維護一套備用棧,監聽頁面路由,每次進入新頁面時,記錄當前頁面的URL,當退出時,删除記錄的URL,在瀏覽器刷新棧記錄失效時,幫助回退到上一頁。

(二)方案優缺點

優點: 可實現回退效果無异常,調用Navigator.pop方法或點擊瀏覽器回退按鈕都支持;
缺點: Navigator.pushName().then的回調無法生效,因為是重新生成的上一頁,所以並不會調用回調;回退後的頁面中的臨時數據都會消失,比如輸入框內的內容,成員變量等;跳轉必須用靜態路由的方式,並且傳參要用Uri包裹,不能用構造函數傳參。

實現

1. Web本地存儲工具—localStorage

localStorage是在html包下window中的一個存儲對象,以key、value的形式進行存儲

// 導包
import 'dart:html' as html;
// 使用方式
html.window.localStorage["key"] = "value"

對存儲工具的封裝這裏就不寫到文章裏了,根據實現業務情况去封裝,方便調用就行。

2. 棧記錄工具類RouterHistory
這是一個棧記錄工具,主要作用是注册監聽,添加删除記錄等。

/// DB()為封裝好的本地數據庫
class RouterHistory {
  /// 監聽瀏覽器刷新前的回調
  static Function(html.Event event)? _beforeUnload;

  /// 監聽瀏覽器回退時的回調
  static Function(html.Event event)? _popState;

  /// 目前頁面是否被刷新過
  static bool isRefresh = false;

  /// 初始化與注册監聽
  static void register() {
    // 刷新時回調
    _beforeUnload = (event) {
      // 本地記錄,標記成"已刷新"
      DB(DBKey.isRefresh).value = true;
      // 移除刷新前的實例的監聽
      html.window.removeEventListener('beforeunload', _beforeUnload);
      html.window.removeEventListener('popstate', _popState);
    };
    // 瀏覽器回退按鈕回調
    _popState = (event) {
      // 頁面被刷新,觸發備用回調
      if (isRefresh) {
        _back(R.currentContext); //R.currentContext 為當前頁面的Context
      }
    };
    // 添加監聽
    html.window.addEventListener('beforeunload', _beforeUnload);
    html.window.addEventListener('popstate', _popState);

    // 從本地數據庫中取出"刷新"標記
    isRefresh = DB(DBKey.isRefresh).get(false);
    
    // 如果未被刷新,清除上次備用棧中的曆史記錄
    if (!isRefresh) {
      clean();
    }

    // 還原本地庫中的刷新標記
    DB(DBKey.isRefresh).value = false;
  }
  
  
  static bool checkBack(currentContext) {
    // 是否能正常 pop
    if (Navigator.canPop(currentContext)) {
      return true;
    }

    // 不能則啟用備用棧
    _back(currentContext);
    return false;
  }

  // 返回
  static void _back(currentContext) {
    List history = get();
    if (history.length > 1) {
      history.removeLast();
      set(history);
      //跳轉至上一頁並關閉當前頁
      Navigator.of(currentContext).popAndPushNamed(history.last);
    }
  }

  // 添加記錄
  static add(String? path) {
    if (path == null) return;
    List history = get();
    if (history.contains(path)) return;
    history.add(path);
    set(history);
  }

  // 删除記錄
  static remove(String? path) {
    if (path == null) return;
    List history = get();
    history.remove(path);
    set(history);
  }

  // 設置備用棧數據
  static String set(List<dynamic> history) => DB(DBKey.history).value = json.encode(history);

  // 取出備用棧數據
  static get() => json.decode(DB(DBKey.history).get('[]'));

  // 清除備用棧
  static clean() => DB(DBKey.history).value = '[]';
}

3. 監聽Flutter路由
自定義類並實現NavigatorObserver,並將實現類放在MaterialApp中的navigatorObservers參數中。

// 實現類
class HistoryObs extends NavigatorObserver {
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    // 存路由信息
    RouterHistory.add(route.settings.name);
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    // 删路由信息
    RouterHistory.remove(route.settings.name);
  }
}


// 設置監聽
MaterialApp(
    ......
    navigatorObservers: [ HistoryObs() ],
    ......
)

4. 路由方法封裝
跳轉方法必須為靜態路由,以保證參數和路徑都能在url中,才可實現回退效果

 /// 替換 Navigator.pop ,
  static pop() {
    // 檢測是否能正常返回,不能則返回FALSE
    if (RouterHistory.checkBack(currentContext)) {
      Navigator.pop(currentContext);
    }
  }

  /// 靜態路由跳轉
  static Future toName(String pageName, {Map<String, dynamic>? params}) {
    // 封裝路徑以及參數
    var uri = Uri(scheme: RoutePath.scheme, host: pageName, queryParameters: params ?? {});
    return Navigator.of(currentContext).pushNamed(uri.toString());
  }

5. 初始化比特置
放在MaterialApp外層的build中,或initState中即可。

  @override
  void initState() {
    super.initState();
    RouterHistory.register();
  }

  @override
  Widget build(BuildContext context) {
    // 或 RouterHistory.register();
    return MaterialApp(
          navigatorObservers: [MiddleWare()],
    );
  }

以上就是該方案的關鍵代碼

最後

該方案只是能解决問題,但不是最好的解决方案。有更好的解决方案歡迎留言~

Flutter官方的Navigator 2.0 雖然能實現回退,本質上也是跳轉了新頁面,並造成棧內記錄混亂,不能像真正的web一樣,感興趣的同學可以自行了解下Navigator 2.0

Navigator2.0在瀏覽器回退按鈕的處理上又與Navigator1.0不同,點擊回退按鈕時Navigator2.0並不是執行pop操作,而是執行setNewRoutePath操作,本質上應該是從瀏覽器的history中獲取上一個頁面的url,然後重新加載。這樣確實解决了刷新後回退的問題,因為刷新後瀏覽器的history並未丟失,但是也導致了文章中我們提到的flutter中的頁面棧混亂的問題。

如果你進階的路上缺乏方向,可以掃描下方二維碼加入我們的圈子和安卓開發者們一起學習交流!

-以下全部內容都可以在微信中獲取!

Android進階學習全套手册
img

  • Android對標阿裏P7學習視頻
    img

  • BATJ大廠Android高頻面試題
    img

作者:蘇啵曼
鏈接:https://juejin.cn/post/7114848130004156446

原网站

版权声明
本文为[代碼不難寫]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/187/202207060938118178.html