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