質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.81%

flutterでflutter_webview_pluginを使ってoauthログインを実装したい

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 691

sagme

score 9

前提・実現したいこと

アプリの初期画面から、webviewで認証サーバのログイン画面を呼び出してログインし
callback画面を経て、次の画面に遷移しようとしています。

flutter_webview_pluginを使って、callback画面にwebviewが変化した際のイベントを検知して
遷移後のURLを取得して、そこから認証コードを取得する方法で実装しました。

画面遷移図は↓になります。
イメージ説明

発生している問題・エラーメッセージ

◎ 戻るボタンを押すと上手くいかなくなる

初期画面から、認証画面を呼び出して一度も戻らずにログインした場合は、
レスポンスを取得してscreen1に画面遷移するのですが、

認証画面でAppbarの戻るボタン(←)を押し、一度初期画面に戻ってから
再び遷移した認証画面でログインすると、callback画面に遷移まではするのですが
Screen1への画面遷移が行われません。
イメージ説明

エラーメッセージは特に無いです。

該当のソースコード

main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:flutter_http_test04/screen1.dart';

// 認証サーバ情報
String _authUrl = 'http://.../auth';    //認証サーバのURL
String _clientId = 'test_id1';          // 認証サーバに登録したクライアントID
String _redirectUrl = 'http://.../blank // callback画面のURL

String _responseType = 'code';
String _scope = 'openid offline_access';
// 認証サーバを呼び出す際のリクエストURL
String _pageUrl = '${_authUrl}?client_id=${_clientId}&redirect_uri=${_redirectUrl}&response_type=${_responseType}&scope=${_scope}';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final flutterWebViewPlugin = FlutterWebviewPlugin();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

      routes: {
        '/': (_) => MyHomePage(title: 'Flutter WebView Demo'),
        '/login': (_) {
          return WebviewScaffold(
            url: _pageUrl,
            mediaPlaybackRequiresUserGesture: false,
            appBar: AppBar(
              title: const Text('Widget WebView'),
            ),
            withZoom: true,
            withLocalStorage: true,
            hidden: true,
            initialChild: Container(
              color: Colors.white30,
              child: const Center(
                child: Text('Waiting.....'),
              ),
            ),
          );
        },
      }
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final flutterWebViewPlugin = FlutterWebviewPlugin();
    // On urlChanged stream
  StreamSubscription<String> _onUrlChanged;

  String _code = '';                      // 認証コード取得用
  String _session_state = '';             // session_tate取得用
  String _url = '';                       // webView URL確認用
  bool _onetime_flg = false;              // onUrlChanged イベント制御用


  @override
  void initState() {
    super.initState();
    print('init 1');
    // flutterWebViewPlugin.close();      // このタイミングではあってもなくても変わらなかった
    // _urlget();                        // 最初はinitstateでurlを取ろうとしたが、戻るボタンを押した後の遷移でinitstate通らないからやめた

  }

  void _urlget() {
    print('url get');
    _onetime_flg = false;                 // 2回連続でonUrlChangedが呼び出された時、最初の1回だけ通す用 

    // Add a listener to on url changed
    _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen(( String url) {  // 最初のwebview表示後は、ログイン画面とscreen1画面でなぜか2回ずつイベントをキャッチする
                                                                              // 戻るボタンを押した後もう一度ログイン画面に遷移した後はイベントをキャッチしない 
      print('mounted=$mounted');
      print('_onetime_flg=$_onetime_flg');
      if (mounted && _onetime_flg == false) {                 // widgetツリーに存在しているかどうか & 1回目のonUrlChangedイベントの時
        setState(() {
          _onetime_flg = true;

          _url = url.split('?')[0];                           // 現在のurl確認用に取得
          var tmp = url.split('?')[1];                        // urlの後ろのレスポンスを取得
          print('=$tmp');
          if (tmp.startsWith('session_state')) {              // 認証後のcallback画面に遷移してsession_stateが返ってきた時
            _session_state = tmp.split('&')[0].split('=')[1]; // データAPIアクセス用のsession_state保存
            _code = tmp.split('&')[1].split('=')[1];          // データAPIアクセス用の認証コード保存

            // 一旦初期画面に戻る
            print('pop');
            Navigator.of(context).pop();                      // 初期画面に戻らないと、screen1に画面遷移した後、pushReplacementが有効にならない
          }

          print('url=$_url');
          print('session_state=$_session_state');
          print('code=$_code');

          // 次画面呼び出し
          if  (_session_state.isNotEmpty && _code.isNotEmpty) {// session_stateと認証コードが取れている時だけscreen1に遷移する
            print('3');
            _gotoScreen1();
          }
        });
      }
    });

    var tmp2 = _onUrlChanged.toString();                      // _onUrlChangedって何が返ってきてるのか調べようとした → よくわからなかった
    print('end _onUrlChanged=$tmp2');
  }

  // 認証サーバの画面呼び出し ログイン済みの場合は直接コールバック画面に飛ぶ
  void _loginFunc() async {
    print('loginFunc');
  //  flutterWebViewPlugin.dispose();                         //webviewを作り直そうとして、このタイミングでclose(),dispose()を入れたが、認証後に画面遷移しなくなる
    Navigator.of(context).pushNamed('/login');
    //Navigator.of(context).pushReplacementNamed("/login");   // 最初から戻るボタンなしにすると認証後に画面遷移しない

  }

  // 画面遷移 screen1 認証完了後に遷移する画面
  void _gotoScreen1() {
    print('goto screen1');
    Navigator.pushReplacement(                                // 初期画面やログイン画面に戻りたくない 認証後はここがメインページになる
      context,
      new MaterialPageRoute<Null>(
        settings: const RouteSettings(name: "/screen1"),
        builder: (BuildContext context) => Screen1(session_state: _session_state, code:_code),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    print('widget');
    if (_session_state.isEmpty) {
      _urlget();
    }

    return Scaffold(
      appBar: AppBar(title: Text(widget.title),),
      body: Center(

        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            Padding(
              padding: EdgeInsets.only(bottom: 40),
              child: Text('アプリ初期画面'),
            ),

            FlatButton(key:null, onPressed: _loginFunc,
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0)),
              color: Color(0xFF4c6cb3),             // 群青色
              child: Text('ログインページへ',),
            ),

          ],
        ),
      ),

    );
  }
}
screen1.dart

import 'package:flutter/material.dart';


//// widget ////
class Screen1 extends StatefulWidget {
  String session_state;
  String code;

  Screen1({Key key, this.code ,this.session_state}): super(key: key);

  @override
  _Screen1State createState() => _Screen1State(this.session_state, this.code);
}

class _Screen1State extends State<Screen1> {
  String _session_state;
  String _code;

  _Screen1State(String sesssion_state, String code){
    this._session_state = sesssion_state;
    this._code = code;
  }

  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(title: Text('screen1'),),
      body: Column(
        children: [
          // 前画面で取得したsession_stateと認証コードを表示する
          Text('screen1'),
          Text('$_session_state'),
          Text('$_code'),
        ]
      )
    );
  }
}

試したこと

print文を入れて、コードがどのように流れているのかを追ってみたのですが
2度目の認証画面を表示した際には、プラグインのonUrlChangedが画面の変化を認識しないため
URLの取得や、その後の画面遷移が行われない事がわかりましたが
なぜ2度目の画面表示ではonUrlChangedが動かないのかわかりません。

補足情報(FW/ツールのバージョンなど)

macOS Mojave
Flutter 1.12.13+hotfix.5
Tools • Dart 2.7.0

説明が長くなってしまいましたが、どうぞよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正の依頼

  • sagme

    2020/05/08 15:00

    AppAuth pluginを試した時と、今回の自サーバはhttp接続です。
    今回はAWS上にあり、AppAuth pluginの時はDockerでローカルPC上に立てましたが
    どちらもブラウザ(chrome)からアクセスした場合は普通に動作することを確認しました。

    キャンセル

  • nakasho_dev

    2020/05/08 18:53

    AndroidやiOSのアプリの設定にhttpでも通信できる設定はしましたか。デフォルトではHTTPS通信、かつ、妥当性のあるSSL証明書でないと通信できず、設定が必要です。

    キャンセル

  • sagme

    2020/05/08 19:30 編集

    戻るボタンを押さないで画面遷移する場合は、レスポンスが取れているため通信の設定の問題では無いような気がしますが、確認してみます。

    キャンセル

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.81%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る