【Ionic】メニュー内で画面遷移するサイドメニュー、ナビゲーションメニュー、ドロワーを実装する

Webアプリやスマホアプリで実現したいUIの作り方を検索するときのキーワードに悩むことが多い。画面左上(または右上)のアイコンをクリックすると左からスライドして表示されるメニューって、人によって呼び方が違うが何と呼ぶのが正解なのだろう。

サイドメニュー?
ナビゲーションメニュー?
ドロワー?

やりたかったことは、そのメニューの中で画面が遷移するというもの。例えば検索条件を絞り込みしたいときなど。
f:id:yoppy0066:20210324022441p:plain

こういう画面を実現したいコンポーネントやライブラリなどはGItHubを探しても意外と見つかりませんでした。そこでさらに探してなるほどとなる。Angularのアニメーションを利用して独自に実装するのがよさそう。
github.com

ありがちなUIだと思っていたが標準コンポーネントにないということはあまりメジャーではないのだろうか。

flutter StreamBuilderから遷移した画面のStreamBuilder内のTextFieldを初期化する

困った。StreamBuilderで作ったList画面から、これまたStremBuilderで作ったEdit画面へ画面遷移。

Edit画面のStreamBuilder内にTextFieldを置いて、Streamから取得した値で初期化しようとすると、キーボードを表示する度にStreamが流れてしまい、編集しようとも初期化しようとする値に戻ってしまう。

EditScreenのStremが流れるたびに呼び出し元のListScreenにもStreamが流れてしまい、想定した動きにならない。

仕方ないのでEditScreenに初期化済み判定用のフラグを用意してみた。

class ListScreen extends StatelessWidget {

  // APIからデータを取得してストリームに流し込むことを想定
  Stream<List<String>> _stream = Stream<List<String>>.value(
      ["Title1", "Title2", "Title3", "Title4", "Title5"]);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("First Screen")),
        body: StreamBuilder(
          stream: _stream,
          builder: (context, AsyncSnapshot<List<String>> snapshot) {
            if (!snapshot.hasData) {
              return Container();
            }

            return ListView.builder(
                itemCount: snapshot.data.length,
                itemBuilder: (context, index) {
                  return InkWell(
                    onTap: () {
                      Navigator.of(context).push(MaterialPageRoute(
                        builder: (context) => DetailScreen(),
                      ));
                    },
                    child: Card(
                      child: Container(
                          margin: EdgeInsets.all(10.0),
                          child: Text(snapshot.data[index])),
                    ),
                  );
                });
          },
        ));
  }
}

class DetailScreen extends StatefulWidget {
  DetailScreen();

  @override
  State<StatefulWidget> createState() => DetailScreenState();
}

class DetailScreenState extends State<DetailScreen> {
  // APIから取得してデータをストリームに流し込むことを想定
  Stream<String> _stream = Stream<String>.value("this is id='s data");

  // TextFieldを初期化済みかどうかを管理するためのフラグ
  bool _initialize = false;

  final _titleTextController = TextEditingController();

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Second Screen")),
      body: StreamBuilder(
          stream: _stream,
          builder: (context, AsyncSnapshot<String> snapshot) {
            if (!snapshot.hasData) {
              return Container();
            } else if (!_initialize) {
              // ストリームから流れてきたデータをTextFieldの初期値にセット
              // ただしSoftwareKeyboardの表示・非表示が切り替わるたびにStreamが
              // 流れてくるため、初期化済みか判定用のフラグ管理する
              _titleTextController.text = snapshot.data;
              _initialize = true;
            }
            return Container(
              child: Column(
                children: <Widget>[
                  TextFormField(
                    controller: _titleTextController,
                    decoration: InputDecoration(labelText: "Title"),
                  ),
                ],
              ),
            );
          }),
    );
  }
}

これはもうちょっと使い方調べないとダメやな。
provider packageのStreamProvider使って同じことしてみる。

dart catchErrorのチェーン

catchErrorをチェーンしたときの理解があやしかったので試した

void main() async {

  // catchErrorで例外を投げる
  _future(true)                    // o:実行される x:されない
      .catchError((e) => throw e)  // x
      .then((v) => _future(v))     // o
      .catchError((e) => throw e)  // o
      .then((v) => _future(v))     // x
      .catchError((e) => throw e); // o

  // catchErrorで何もしない
  _future(true)
      .catchError((e) => {})       // x
      .then((v) => _future(v))     // o
      .catchError((e) => {})       // o
      .then((v) => _future(v))     // o
      .catchError((e) => {});      // o
}

Future<bool> _future(bool b) async {
  if (b) {
    return false;
  } else {
    throw Exception();
  }
}

catchErrorで例外を投げると次のcatchErrorは実行されてしまう。例外投げたら処理中断したい場合が多いと思うのでcatchErrorを複数チェーンするのはやめたほうが良さそう。素直にこう書く

try {
  _future(true)
      .then((v) => _future(v))
      .then((v) => _future(v));
} catch(e) {
}

以上です

dart Future型を戻り値とする関数(メソッド)

Futureを返すメソッドの理解があやしかったので実験してみた
バージョン : Dart 2.6.0 (build 2.6.0-dev.1.0 d6c6d12ebf)

Future<int>

Future<int> _future() {
  // OK
  // return Future.value(1);

  // Warning : info: This function has a return type of 'Future<int>'
  // 何も返さない

  // NG : error: The return type 'int' isn't a 'Future<int>'
  // return 1;
}

Future<int> async

Future<int> _future() async {
  // OK
  // return 1;
  // return Future.value(1);

  // Warning : info: This function has a return type of 'Future<int>'
  // 何も返さない
}

Future<void>

Future<void> _future() {
  // OK
  // return Future.value(null);

  // Warning : info: This function has a return type of 'Future<void>'
  // 何も返さない

  // NG : error: Missing return value after 'return'
  // return;
}

Future<void> async

Future<void> _future() async {
  // OK
  // return;
  // 何も返さない
  // return Future.value(null);
}

・結論
async つければ Futureかえしても値をそのまま返しても良いみたい。Futureつけるときはasyncつけて簡潔に書く。以上です

flutter ビルドできないときに確認すること

Android Studioでflutterアプリを実機にインストールできないときの備忘録

xcodeのSigning Teamが選択されていない

・現象
iOSの実機にインストールできない

エラー

2019-10-17 13:16:41.773 ios-deploy[39315:228336] [ !! ] Error 0xe80000be: This application's application-identifier entitlement does not match that of the installed application. These values must match for an upgrade to be allowed. AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0)
Could not install build/ios/iphoneos/Runner.app on 53d01d7b3b0eb6a24f2adb9d9f99c212573a7d16.
Try launching Xcode and selecting "Product > Run" to fix the problem:
  open ios/Runner.xcworkspace

Error launching application on xxxxxのiPhone.

・解決策
xcodeの「Signing & Capabilities」より「Team」を選択する

flutter が違う証明書を使用している

・現象
xcodeから直接ビルドはできるが、AndroidStudioからビルドできない

エラー

Error output from Xcode build:
↳
    2019-10-17 13:21:23.638 xcodebuild[40625:235490]  DVTProvisioningProfileManager: Failed to load profile "/Users/xxxxx/Library/MobileDevice/Provisioning Profiles/xxxxx.mobileprovision" (Error Domain=DVTProvisioningProfileProviderErrorDomain Code=1 "Failed to load profile." UserInfo={NSLocalizedDescription=Failed to load profile., NSLocalizedRecoverySuggestion=Profile is missing the required UUID property.})
    2019-10-17 13:21:29.655 xcodebuild[40625:235483]  DVTPortal: Service '<DVTPortalAppIDService: 0x7f9fc2ef4680; action='addAppId'>' encountered an unexpected result code from the portal ('9401')
    2019-10-17 13:21:29.655 xcodebuild[40625:235483]  DVTPortal: Error:
    Error Domain=DVTPortalServiceErrorDomain Code=9401 "An App ID with Identifier 'xxx.yyy.zzz' is not available. Please enter a different string." UserInfo={payload=<CFBasicHash 0x7f9fc2ef5260 [0x7fff9505d8e0]>{type = mutable dict, count = 10,
    entries =>
    	0 : <CFString 0x7f9fc2ef5f70 [0x7fff9505d8e0]>{contents = "requestId"} = <CFString 0x7f9fc2ef53f0 [0x7fff9505d8e0]>{contents = "C94C1E95-4601-46B6-919C-09E769FAA274"}
    	1 : responseId = <CFString 0x7f9fc2ef5670 [0x7fff9505d8e0]>{contents = "c0e66a1e-a862-4052-9988-5a8c0c8d2165"}
    	2 : <CFString 0x7fff950ba5b8 [0x7fff9505d8e0]>{contents = "protocolVersion"} = QH65B2
    	3 : <CFString 0x7f9fc2ef5550 [0x7fff9505d8e0]>{contents = "requestUrl"} = <CFString 0x7f9fc2ef5590 [0x7fff9505d8e0]>{contents = "https://developerservices2.apple.com/services/QH65B2/ios/addAppId.action"}
    	6 : <CFString 0x7f9fc2ef54f0 [0x7fff9505d8e0]>{contents = "userLocale"} = en_US
    	8 : resultCode = <CFNumber 0x160cc8f9a965e017 [0x7fff9505d8e0]>{value = +9401, type = kCFNumberSInt64Type}
    	9 : userString = <CFString 0x7f9fc2ef3d80 [0x7fff9505d8e0]>{contents = "An App ID with Identifier 'xxx.yyy.zzz' is not available. Please enter a different string."}
    	10 : <CFString 0x7f9fc2ef2a80 [0x7fff9505d8e0]>{contents = "resultString"} = <CFString 0x7f9fc2ef3d80 [0x7fff9505d8e0]>{contents = "An App ID with Identifier 'xxx.yyy.zzz' is not available. Please enter a different string."}
    	11 : httpCode = <CFNumber 0x160cc8f9a9419117 [0x7fff9505d8e0]>{value = +200, type = kCFNumberSInt64Type}
    	12 : <CFString 0x7f9fc2ef5eb0 [0x7fff9505d8e0]>{contents = "creationTimestamp"} = <CFString 0x7f9fc2ef5f10 [0x7fff9505d8e0]>{contents = "2019-10-17T04:21:29Z"}
    }
    , NSLocalizedDescription=An App ID with Identifier 'xxx.yyy.zzz' is not available. Please enter a different string.}
    ** BUILD FAILED **


Xcode's output:
↳
    note: Using new build systemnote: Planning buildnote: Constructing build descriptionerror: Failed to register bundle identifier. The app identifier "xxx.yyy.zzz" cannot be registered to your development team. Change your bundle identifier to a unique string to try again. (in target 'Runner' from project 'Runner')error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't support the Associated Domains and Push Notifications capability. (in target 'Runner' from project 'Runner')error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't include the aps-environment and com.apple.developer.associated-domains entitlements. (in target 'Runner' from project 'Runner')warning: The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address deprecated @objc inference warnings, test your code with “Use of deprecated Swift 3 @objc inference” logging enabled, and then disable inference by changing the "Swift 3 @objc Inference" build setting to "Default" for the "Runner" target. (in target 'Runner' from project 'Runner')

Could not build the precompiled application for the device.

Error launching application on xxxxxのiPhone.

・解決策
flutter run コマンドで証明書が選択できる

$ flutter run -t lib/main.dart

Launching lib/main.dart on xxxxxのiPhone in debug mode...
Multiple valid development certificates available (your choice will be saved):
  1) iPhone Developer: xxxxx (xxxxx)
  2) iPhone Developer: xxxxx (xxxxx)
  3) iPhone Developer: xxxxx (xxxxx)
  a) Abort
Please select a certificate for code signing [1|2|3|a]: 

設定されている証明書を変更したい場合は1度クリアしてからflutter runする

$ flutter config --clear-ios-signing-cert

AndroidStudioのRunボタンが押せない

・現象
Runボタン(右向三角の再生ぽいやつ)が押せない
Run/Debug Configurationsが設定されていないから

・ここで設定
f:id:yoppy0066:20191018120225p:plain

Dart Endpointを設定
f:id:yoppy0066:20191018120246p:plain

以上です

flutter android studioでdebug printが表示されない

iPhoneにビルドしてdebug printを確認したかったのだがAndroidStudioのコンソールになかなか表示されず躓いた

うまくいかないやつ

print("表示されない");
debugPrint("これも表示されない");

うまくいったやつ

import 'dart:developer';
・・・
log("やっと表示された");

ちなみに上の記述ではRunでもDebugでのビルドでもダメだった。あと、細かい挙動としてRunでビルドした場合はコンソールの「Run」に表示されて。Debugでビルドした場合はコンソールの「Logcat」に表示された

以上です

ionic(cordova)でsignin with appleを実装するプラグイン

iOS13のリリースも近いがcordovaアプリでも当然実装しないとならなくなりそう
ionicで標準装備してくれるとありがたいが、まだなさそうなのでサードパーティプラグインを使うしかなさそう

github.com
こちらで

以上です