FlutterでQiitaのアプリを作ってみる その4

前回まではこちら

https://qiita.com/t0yger/items/c1ea368aab91c1572ccc

https://qiita.com/t0yger/items/9fe42fe04e5636ae9b3d

https://qiita.com/t0yger/items/2d65ad3bd66154fd2883

repository: 

https://github.com/t0yger/qiita-app

前回の続きで記事をWebViewに表示させるところからやります
pluginの[webview_flutter](https://pub.dev/packages/webview_flutter)というものがあり、こちらを導入します。

https://pub.dev/packages/webview_flutter

またこちらは[codelab](https://codelabs.developers.google.com/codelabs/flutter-webview?hl=ja#0)もあったのですが, 3.x系の内容で現在の4.x系ではなかったのでREADMEを読みながら導入していきたいと思います

## webview_flutter
まずは以下コマンドで追加
```sh
flutter pub add webview_flutter
```
追加したらios/androidの`MinimumOSVersion/minSdkVersion`を確認する
webview_flutterの現在の最新(4.0.6)ではiosは11以上、androidは19以上が必須になっているので必要であれば更新する
まずはios
該当箇所は`ios/Flutter/AppFrameworkInfo.plist`の中の`MinimumOSVersion`
このアプリの場合は11.0になっていたので特に変更なくこのまま
続いてandroid
該当箇所は`android/app/build.gradle`の`minSdkVersion`
`flutter.minSdkVersion`となっている。具体的な値は何になっているのだろうか
`local.properties`の中身を確認しても定義されていない。分からなかったので無難に20に変更
flutterのsdk側で定義されてるのかな？
とりあえずこれで使えるようになったはず

README通り`controller`を定義して`WebViewWidget`に渡す
以下の感じになった
```dart
class ArticleWebView extends StatelessWidget {
  const ArticleWebView({super.key});

  static const routeName = '/article';

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as ArticleArguments;
    final controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(args.url));
    return Scaffold(
      appBar: AppBar(
        title: Text('article'),
      ),
      body: WebViewWidget(controller: controller),
    );
  }
}
```
必要最小限だとこんな感じですかね

表示は出来ましたが、もう少しwebviewを触ってみます
表示までに読み込みが結構かかります。progressを出したい
`CircularProgressIndicator`と`LinearProgressIndicator`などが検索すると出てきました
WebViewなので`LinearProgressIndicator`の方がっぽい気がするのでこちらを採用します
webviewのREADMEにあった`onProgress`が使えそうなので試してみる
controllerに追加してどのような値が入ってくるのか確認します
```dart
..setNavigationDelegate(NavigationDelegate(
        onProgress: (int progress) {
          print(progress);
        },
      ))
```
実行しwebviewを開くと以下のログが
```
flutter: 10
flutter: 30
flutter: 48
flutter: 89
flutter: 90
flutter: 90
flutter: 90
flutter: 90
flutter: 90
flutter: 90
flutter: 100
```
やはりLoadingの進捗度合いが取れそうなのでこれを使う
またこのwebviewを使ってるwidgetがstatelessなのでstatefulに変更する
```dart
class ArticleWebView extends StatefulWidget {
  const ArticleWebView({super.key});
  static const routeName = '/article';

  @override
  State<ArticleWebView> createState() => _ArticleWebView();
}

class _ArticleWebView extends State<ArticleWebView> {
  int _progress = 0;

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as ArticleArguments;
    final controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(NavigationDelegate(
        onProgress: (int progress) {
          print(progress);
          _progress = progress;
        },
        onPageStarted: (url) {
          print('start');
        },
        onPageFinished: (url) {
          print('finish');
        },
      ))
      ..loadRequest(Uri.parse(args.url));

    return Scaffold(
        appBar: AppBar(
          title: Text('article'),
        ),
        body: Column(
          children: [
            LinearProgressIndicator(value: _progress.toDouble()),
            Expanded(child: WebViewWidget(controller: controller)),
          ],
        ));
  }
}
```
これで実行してみる
あれ？終わらない...

```
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: start
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: start
flutter: 10
flutter: 10
flutter: start
flutter: 10
flutter: 10
flutter: 30
flutter: 30
flutter: 10
flutter: 10
flutter: 10
flutter: 10
flutter: 30
flutter: 10
flutter: 10
flutter: 10
flutter: start
flutter: 10
flutter: 10
flutter: start
flutter: 10
flutter: 10
flutter: start
flutter: start
flutter: 10
flutter: 10
flutter: start
flutter: start
flutter: start
flutter: 10
flutter: start
flutter: 10
flutter: 46
flutter: 86
flutter: 10
...
```
`build`の中で`controller`を定義しているから`_progress`の値が変わる度に`build`が呼ばれて`controller`が定義され直して？終わらない感じっぽい
困った。urlは引数として前の画面から受け取らないとなので`build`の中で定義してたのだが分割しよう
試行錯誤で出来たコードが以下
```dart
class ArticleWebView extends StatefulWidget {
  const ArticleWebView({super.key});
  static const routeName = '/article';

  @override
  State<ArticleWebView> createState() => _ArticleWebView();
}

class _ArticleWebView extends State<ArticleWebView> {
  double _progress = 0.0;
  bool _isFirst = true;
  bool _isLoading = false;
  final controller = WebViewController()
    ..setJavaScriptMode(JavaScriptMode.unrestricted);

  @override
  void initState() {
    super.initState();
    controller.setNavigationDelegate(NavigationDelegate(
      onPageStarted: (url) {
        if (mounted) {
          setState(() {
            _isLoading = true;
          });
        }
      },
      onPageFinished: (url) {
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
      },
      onProgress: (progress) {
        if (mounted) {
          setState(() {
            _progress = progress / 100;
          });
        }
      },
    ));
  }

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as ArticleArguments;
    if (_isFirst) {
      _isFirst = false;
      controller.loadRequest(Uri.parse(args.url));
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('article'),
      ),
      body: Column(
        children: [
          _isLoading
              ? LinearProgressIndicator(value: _progress)
              : const SizedBox.shrink(),
          Expanded(child: WebViewWidget(controller: controller)),
        ],
      ),
    );
  }
}
```
ほ、本当に？`_isFirst`とかいつの時代だよとPRに書かれそう
これどうするのが正解なのか有識者の方教えてください。お願いします:bow:

まあでもとりあえず動いたから良し

なんか軽い気持ちでProgress出したかったのに意外と大変だった。多分知らないだけなんだろうけど。
今回はここまで。後何をやろう...ページング対応してないか
次回はページングやろうと思います

https://qiita.com/t0yger/items/b0bd7b9c70e5267a20b5