Flutter WebView+PullRefresh

この記事は、現状のFlutterSDKで中でAndroid側で発生している問題を回避するだけのものです
## 課題
・iOSはWebViewのGestureを使用したやり方の記事が散見される
・AndroidはWebView＋PullToRefreshがうまく動作しないバグがある

## 私が行った対応
##### iOS
WebView+pullToRefresh
##### android
Stackの中にWebViewとPullToRefreshを別々に入れる

```
class AllowVerticalDragGestureRecognizer extends VerticalDragGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}
```

```
// ignore: must_be_immutable
class WebviewPage extends StatelessWidget {
  WebviewPage({Key? key, required this.initialUrl}) : super(key: key);

  final String initialUrl;
  WebViewController? controller;
  final refreshController = RefreshController();

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        // Androidの戻るボタンをwebviewのbackに反映させる
        if (await controller?.canGoBack() ?? false) {
          controller!.goBack();
        }
        return false;
      },
      child: Scaffold(
        body: Platform.isIOS ? _iosWidget : _androidWidget,
      ),
    );
  }

  Widget get _iosWidget {
    return SmartRefresher(
      onRefresh: () async {
        await controller?.reload();
        refreshController.refreshCompleted();
      },
      controller: refreshController,
      header: Platform.isIOS
          ? CustomHeader(
        builder: (_, __) {
          return const CupertinoActivityIndicator(
            color: Colors.white,
          );
        },
      )
          : const MaterialClassicHeader(),
      child: _customWebView(),
    );
  }

  Widget get _androidWidget {
    return Stack(alignment: AlignmentDirectional.topCenter, children: [
      _customWebView(),
      Positioned(
        top: 0,
        left: 0,
        right: 0,
        height: 80,
        child: IgnorePointer(
          ignoring: true,
          child: SmartRefresher(
            onRefresh: () async {
              await controller?.reload();
              Future.delayed(const Duration(milliseconds: 500)).then((_) {
                refreshController.refreshCompleted();
              });
            },
            controller: refreshController,
            header: Platform.isIOS
                ? CustomHeader(
              builder: (_, __) {
                return const CupertinoActivityIndicator(
                  color: Colors.white,
                );
              },
            )
                : const MaterialClassicHeader(),
            child: Container(
              height: 0,
            ),
          ),
        ),
      ),
    ]);
  }

  Widget _customWebView() {
    final gesture = <Factory<AllowVerticalDragGestureRecognizer>>{};
    gesture.add(Factory<AllowVerticalDragGestureRecognizer>(
          () => Platform.isIOS ? _iosGesture : _androidGesture,
    ));

    return WebView(
      userAgent: Const.userAgent,
      backgroundColor: Colors.black,
      javascriptMode: JavascriptMode.unrestricted,
      gestureRecognizers: gesture,
      onWebViewCreated: (controller) {
        this.controller = controller;
      },
    );
  }

  AllowVerticalDragGestureRecognizer get _iosGesture {
    final gesture = AllowVerticalDragGestureRecognizer();
    bool refreshFlag = false;
    gesture.onDown = (_) {
      controller!.getScrollY().then(
            (value) {
          if (value == 0) {
            refreshFlag = true;
          }
        },
      );
    };
    gesture.onEnd = (_) {
      if (refreshFlag) {
        refreshFlag = false;
        controller!.getScrollY().then((value) {
          if (value < 0) {
            refreshController.requestRefresh();
          }
        });
      }
    };
    return gesture;
  }

  AllowVerticalDragGestureRecognizer get _androidGesture {
    final gesture = AllowVerticalDragGestureRecognizer();
    bool refreshFlag = false;
    gesture.onStart = (startDetails) {
      controller!.getScrollY().then(
            (value) {
          if (value == 0) {
            refreshFlag = true;
          }
        },
      );
    };
    gesture.onEnd = (DragEndDetails dragEndDetails) {
      if (refreshFlag) {
        refreshFlag = false;
        controller!.getScrollY().then((value) {
          if (dragEndDetails.primaryVelocity == 0) {
            return;
          }
          if (value == 0) {
            refreshController.requestRefresh();
          }
        });
      }
    };
    return gesture;
  }
}
```