「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfrastructure.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。

-[[戻る>Flutter]]
--[[Flutterのファースト・ステップ]]
--Flutterのセカンド・ステップ
--[[Flutterのサード・ステップ]]
--[[Flutterの4thステップ]]
--[[Flutterの5thステップ]]

*目次 [#ce05a8e7]
#contents

*概要 [#k43eab95]
[[Flutter]] の step by step(其の二)。

*手順1:プラットフォーム固有のカスタムコード [#q8ddd39b]
Platform Channelsには、3つのチャネルがある。
-[[MethodChannel>#c13f3030]]
-[[EventChannel>#rc8721c3]]
-[[MessageChannel>#t72d28fb]]

**MethodChannelでバッテリーレベルを取得・表示 [#c13f3030]
-P/Invokeの非同期版のような仕組み。
-MethodChannelで、Android(Kotlin or Java)iOS(Swift or Objective-C)に非同期に遷移。
--MethodChannel "アプリパッケージ名/チャンネル名"でハンドラを登録
--ハンドラ内を if(MethodCall.method == "xxxx") で分岐させ、ネイティブAPI呼出。

***main.dart [#kf40d900]
 import 'dart:async';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 ...
 class _MyHomePageState extends State<MyHomePage> {
   static const platform = const MethodChannel('samples.flutter.dev/battery'); 
 
   // Get battery level.
   String _batteryLevel = 'Unknown battery level.';
 
   Future<void> _getBatteryLevel() async {
     String batteryLevel;
     try {
       final int result = await platform.invokeMethod('getBatteryLevel');
       batteryLevel = 'Battery level at $result % .';
     } on PlatformException catch (e) {
       batteryLevel = "Failed to get battery level: '${e.message}'.";
     }
     setState(() {
       _batteryLevel = batteryLevel;
     });
   }
 
 Widget build(BuildContext context) {
   ...
     ElevatedButton(
       child: const Text('BatteryLevel Button'),
       style: ElevatedButton.styleFrom(
         primary: Colors.orange,
         onPrimary: Colors.white,
       ),
       onPressed: _getBatteryLevel,
     )
   ...
 }

***Android [#b5f29a38]
-BatteryManager API
-MainActivity.ktファイル

 package com.opentouryo.flutter_template
 
 import androidx.annotation.NonNull
 import io.flutter.embedding.android.FlutterActivity
 import io.flutter.embedding.engine.FlutterEngine
 import io.flutter.plugin.common.MethodChannel
 
 import android.content.Context
 import android.content.ContextWrapper
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.BatteryManager
 import android.os.Build.VERSION
 import android.os.Build.VERSION_CODES
 
 class MainActivity: FlutterActivity() {
     private val CHANNEL = "samples.flutter.dev/battery"
 
     override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
         super.configureFlutterEngine(flutterEngine)
         MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
             call, result ->
             // Note: this method is invoked on the main thread.
             // TODO
             if (call.method == "getBatteryLevel") {
                 val batteryLevel = getBatteryLevel()
 
                 if (batteryLevel != -1) {
                     result.success(batteryLevel)
                 } else {
                     result.error("UNAVAILABLE", "Battery level not available.", null)
                 }
             } else {
                 result.notImplemented()
             }
         }
     }
 
     private fun getBatteryLevel(): Int {
         val batteryLevel: Int
         if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
             val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
             batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
         } else {
             val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
             batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
         }
 
         return batteryLevel
     }
 }

***iOS [#gfb834e4]
-device.batteryLevel API
-AppDelegate.swiftファイル

 @UIApplicationMain
 @objc class AppDelegate: FlutterAppDelegate {
   override func application(
     _ application: UIApplication,
     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
 
     let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
     let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                               binaryMessenger: controller.binaryMessenger)
     batteryChannel.setMethodCallHandler({
       (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
       // Note: this method is invoked on the UI thread.
       // Handle battery messages.
       guard call.method == "getBatteryLevel" else {
         result(FlutterMethodNotImplemented)
         return
       }
       self?.receiveBatteryLevel(result: result)
     })
 
     GeneratedPluginRegistrant.register(with: self)
     return super.application(application, didFinishLaunchingWithOptions: launchOptions)
   }
 }
 
 private func receiveBatteryLevel(result: FlutterResult) {
   let device = UIDevice.current
   device.isBatteryMonitoringEnabled = true
   if device.batteryState == UIDevice.BatteryState.unknown {
     result(FlutterError(code: "UNAVAILABLE",
                         message: "Battery info unavailable",
                         details: nil))
   } else {
     result(Int(device.batteryLevel * 100))
   }
 }

**EventChannelで... [#rc8721c3]
プラットフォームとFlutter間で双方向でイベント配信を実現するためのAPI

**MessageChannelで... [#t72d28fb]
プラットフォームとFlutter間で双方向でメッセージ送受信するためのAPI

**参考 [#d5db1e90]
-Writing custom platform-specific code - Flutter~
https://flutter.dev/docs/development/platform-integration/platform-channels

-Flutterでプラットフォーム固有APIを~
MethodChannelとEventChannelで実行する | Flutter | nasust dev blog~
https://nasust.com/flutter/e7db909ace82-4d34-af61-a5c95aa3f78e

-Qiita

--kurun_pan
---Flutter MethodChannel APIの使い方~
https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7
---Flutter EventChannel APIの使い方~
https://qiita.com/kurun_pan/items/6d63ebf1e894d3620b20
---Flutter MessageChannel APIの使い方~
https://qiita.com/kurun_pan/items/9c2b6fdba602203f8f83
---Flutter プラットフォーム固有機能を利用するためのSystemChannels APIについて~
https://qiita.com/kurun_pan/items/c0c881f7a3f95ecb3f9d

--FlutterからKotlin/Swiftのネイティブコードを呼んでみた~
https://qiita.com/unsoluble_sugar/items/ae42b5faf52a491f6470
--FlutterでiOSのネイティブコードを呼び出す~
https://qiita.com/niusounds/items/24b2fc70d7f76af4213b

*手順2:[[AppAuth]]で認証処理を実装してみる。 [#t89cc4af]

**準備:カスタムURLスキーム系 [#v560555b]
-PKCE実装のため。

-以下の2つの方式がある。
--[[Private-Use URI Scheme Redirection]]
--[[Claimed Https Scheme URI Redirection]]

***定義 [#b80ff8c2]
-Android~
以下の定義を、src/main/AndroidManifest.xmlに追加する。

--Deep Links
 <!-- Deep Links -->
 <intent-filter>
   <action android:name="android.intent.action.VIEW" />
   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />
   <data android:scheme="myapp" android:host="hoge" />
 </intent-filter>
 
--App Links
 <!-- App Links -->
 <intent-filter android:autoVerify="true">
   <action android:name="android.intent.action.VIEW" />
   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />
   <data android:scheme="http" android:host="example.com" />
 </intent-filter>

--参考
---android studio - Which AndroidManifest.xml to edit in Flutter project? - Stack Overflow~
https://stackoverflow.com/questions/60095118/which-androidmanifest-xml-to-edit-in-flutter-project

-iOS
--Custom URL schemes
--Universal Links

-Web~
なし。

***実装 [#wa3f7484]
ロケーション・バー直では、動かないとのこと。~
以下のHTMLを作成し、ソコから飛んでみる。

-送信
--Deep Links
 <!DOCTYPE html>
 <html>
   <body>
     <a href="myapp://hoge/">App Aに飛ぶ</a>
   </body>
 </html>

--App Links
 <!DOCTYPE html>
 <html>
   <body>
     <a href="http://example.com/">App Aに飛ぶ</a>
   </body>
 </html>

-受信

--Android
---Deep Links
---App Links

--iOS
---Custom URL schemes
---Universal Links

--Web~
onGenerateRouteで実装する。

***参考 [#hcb7c5b2]
-Flutterで作ったAndroidアプリにDeepLinkする方法 - Qiita~
https://qiita.com/Frog_kt/items/5f9e4dd1d2128173f60c

-Deep Links and Flutter applications. How to handle them properly.~
by Aleksandr Denisov | Flutter Community | Medium~
https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283

**準備:Null Safetyを導入 [#cf4abd0d]
サンプルコードが、Null Safetyだったため。

***SDKのアップデート [#x168de8c]
 >flutter channel
 Flutter channels:
   master
   dev
   beta
 * stable

 >flutter upgrade
 Flutter is already up to date on channel stable
 Flutter 2.2.1 • channel stable • https://github.com/flutter/flutter.git
 Framework • revision 02c026b03c (2 days ago) • 2021-05-27 12:24:44 -0700
 Engine • revision 0fdb562ac8
 Tools • Dart 2.13.1

 >flutter doctor
 Waiting for another flutter command to release the startup lock...
 Doctor summary (to see all details, run flutter doctor -v):
 [√] Flutter (Channel stable, 2.2.1, on Microsoft Windows [Version 10.0.19041.985], locale ja-JP)
 [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
     X Android license status unknown.
       Run `flutter doctor --android-licenses` to accept the SDK licenses.
       See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
 [√] Chrome - develop for the web
 [√] Android Studio (version 4.1.0)
 [√] VS Code, 64-bit edition (version 1.47.2)
 [√] Connected device (3 available)
 
 ! Doctor found issues in 1 category.

 >flutter doctor --android-licenses
 All SDK package licenses accepted.======] 100% Computing updates...

 >flutter doctor
 Doctor summary (to see all details, run flutter doctor -v):
 [√] Flutter (Channel stable, 2.2.1, on Microsoft Windows [Version 10.0.19041.985], locale ja-JP)
 [√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
 [√] Chrome - develop for the web
 [√] Android Studio (version 4.1.0)
 [√] VS Code, 64-bit edition (version 1.47.2)
 [√] Connected device (3 available)
 
 • No issues found!

***SDKバージョンを上げる。 [#q90dd5c2]
 environment:
  sdk: ">=2.12.0-0 <3.0.0"

***リビルドする。 [#p773c205]
(flutter pub getの代替)

***コードの修正 [#dd806ce5]
-手動

--型 -> 型?

--XX.YY -> XX?.YY~
null チェックをした上でアクセスする。

--XXX -> XXX ?? ""~
nullの場合、空文字列を返す, etc.

--XXX -> XXX!~
nullでないと断定して non-nullable な型にキャスト。

--requiredを足したり、

--デフォルト値を設定したり、

--, etc.

-自動
 dart migrate

***参考 [#xcd5a51a]
-Flutter2のDart Null Safetyを既存のプロジェクトに導入する | ZUMA Lab~
https://zuma-lab.com/posts/flutter-dart-sound-null-safety-replace

-FlutterはじめたらJavaのClassNotFoundExceptionに遭遇した~
https://zenn.dev/captain_blue/articles/flutter-android-licenses-classnotfound
>Android SDK Command-line Tools(latest)にチェック

**flutter_appauthを使った実装 [#r62dfc2a]
-古い情報に、[[プラットフォーム固有のカスタムコード>#q8ddd39b]]で、~
プラットフォームの[[AppAuth]]を使っているサンプルがあるが、

-最近は、flutter_appauthが利用できるので、~
flutter_appauth[[パッケージを追加する。>Flutterのファースト・ステップ#wdf201cb]]

***IdentityServer4でGetting Started. [#uba632cb]
-Exampleのコードをコピペする。

-redirect_uriが、
 'io.identityserver.demo:/oauthredirect'

>...と、[[Private-Use URI Scheme Redirection]]になってるので、

--Android
---build.gradleファイルに、
 ...
 android {
     ...
     defaultConfig {
         ...
         manifestPlaceholders = [
                 'appAuthRedirectScheme': 'io.identityserver.demo'
         ]
     }
 }

---AndroidManifest.xmlファイルに、~
(API 30以上(Android 11以降))
 <manifest>
   <queries>
       <intent>
           <action android:name="android.intent.action.VIEW" />
           <category android:name="android.intent.category.BROWSABLE" />
           <data android:scheme="https" />
       </intent>
       <intent>
           <action android:name="android.intent.action.VIEW" />
           <category android:name="android.intent.category.APP_BROWSER" />
           <data android:scheme="https" />
       </intent>
   </queries>

--iOS~
Info.plistに、
 <key>CFBundleURLTypes</key>
 <array>
     <dict>
         <key>CFBundleTypeRole</key>
         <string>Editor</string>
         <key>CFBundleURLSchemes</key>
         <array>
             <string>io.identityserver.demo</string>
         </array>
     </dict>
 </array>

>と指定する必要がある。

***[[カスタムURLスキーム>#v560555b]]の重複 [#ybd4585b]
-Android~
以下の両方の設定が有効だった。

--AndroidManifest.xml
--build.gradle

-iOS~
未検証

***HTTPS問題を解消する。 [#p28f5139]
-[[汎用認証サイトでテスト>#o6f153ea]]で検証する場合、

-以下の何れかの方法でHTTPS問題を解決する。

--方法1:~
[[リンク先>スマホの自己署名証明書の許容]]の方法で、自己署名証明書の問題を解決する。~
(プラットフォーム側の証明書検証を無効化する)

--方法2:~
BackendエンドポイントのみHTTPS → HTTP化して問題を解決する。

***汎用認証サイトでテスト [#o6f153ea]
-コードを変更する。~
--Exampleのコード~
エンドポイントのURLを変更
--build.gradle(Android)、Info.plist(iOS)~
カスタムURLスキームを変更

-[[IISのSSL設定>https://techinfoofmicrosofttech.osscons.jp/index.php?IIS%E3%81%AESSL%E8%A8%AD%E5%AE%9A]]を行い、全エンドポイントをHTTPS化した場合、

--以下のエラーが出るので、先ず、CNを一致させる必要がある。
 javax.net.ssl.SSLPeerUnverifiedException: Hostname 192.168.0.x not verified:
 certificate: sha1/****************************
 DN: CN=localhost
 subjectAltNames: [localhost]

--必要に応じて、[[DDNS>Dynamic DO!.jp]]を併用する~
(IPアドレスやNETBIOS名でやった場合、どうなるか?は確認していない)。

--Chromeで、NET::ERR_CERT_COMMON_NAME_INVALIDが出るので、~
SAN(Subject Alternative Name)フィールドを含め生成した。
[[SAN(Subject Alternative Name)フィールドを含め生成>https://techinfoofmicrosofttech.osscons.jp/index.php?IIS%E3%81%AESSL%E8%A8%AD%E5%AE%9A#jab4ab58]]した。

--http/http.dartによる/userinfoへのアクセスだけエラーになったので、
---ココだけHTTP化。~
Flutterのhttp/http.dartにプラットフォーム側の証明書検証の~
無効化が適用されない問題(仕組みがネイティブと異なる)。

---http/http.dartは、[[HTTPClient同様に>https://techinfoofmicrosofttech.osscons.jp/index.php?HttpClient%E3%81%AE%E9%A1%9E%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9#m56ca19e]]無効化可能らしい。~
>java - Flutter https with self signed certificate - Stack Overflow~
https://stackoverflow.com/questions/59622087/flutter-https-with-self-signed-certificate

-BackendエンドポイントのみHTTP化した場合、~
authz、token、userinfoエンドポイントで、以下のエラーが出るので、~
この手法は使用不可能(ライブラリが引数のチェックをしている)。
 Caused by: java.lang.IllegalArgumentException: only https connections are permitted

***参考 [#t2b906d8]
-Flutter Package
--flutter_appauth~
https://pub.dev/packages/flutter_appauth
--flutter_secure_storage~
https://pub.dev/packages/flutter_secure_storage

-Get Started with Flutter Authentication~
https://auth0.com/blog/get-started-with-flutter-authentication/

-Firebase Authenticationを使ってFlutter製アプリに~
Yahoo! JAPAN IDでログインしてみる - Yahoo! JAPAN Tech Blog~
https://techblog.yahoo.co.jp/advent-calendar-2018/firebase-flutter-yid/

*[[Flutterのサード・ステップ]] [#sbd3c5be]

*[[参考>Flutter#a9ed99c8]] [#g074d1d5]

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS