「[[.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]