flutter Widget rebuild statsを確認

Android Studioの話

1. 画面右下の「Flutter Performance」をクリック

f:id:yoppy0066:20190723224545p:plain

2. Widget rebuild stats」を選択して、「Show Widget rebuild information」のチェックを外す

f:id:yoppy0066:20190723224623p:plain

3. ビルドボタンする

f:id:yoppy0066:20190723224649p:plain

4.「Show Widget rebuild information」をチェックする

f:id:yoppy0066:20190723224709p:plain

5. Widgetがリビルドされると該当の箇所がグルグル回るのでわかる

f:id:yoppy0066:20190723224735g:plain

以上です

flutter TabBarViewでListViewを使う

https://qiita.com/Dreamwalker/items/cc19bb4f8b7068ae0fd3

この辺りを参考にタブバーを試していたが、タブを切り替えるたびにTabBarViewのchildrenにセットしたWidgetがinitStateされてしまってListViewが毎回作られてしまった。DefaultTabController使って、StatefulWidgetにはAutomaticKeepAliveClientMixinを適用させてbool get wantKeepAlive => trueするといいらしい。

import 'package:flutter/material.dart';
import 'dart:async';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TabBarDemo(),
    );
  }
}

class TabBarDemo extends StatelessWidget {

  final List<Tab> myTabs = <Tab>[
    Tab(text: 'LEFT'),
    Tab(text: 'RIGHT'),
  ];

  final List<_MyContent> myContents = <_MyContent>[
    _MyContent(),
    _MyContent(),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: myTabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("TabBarSample"),
          bottom: TabBar(tabs: myTabs),
        ),
        body: TabBarView(children: myContents),
      ),
    );
  }
}

class _MyContent extends StatefulWidget {

  _MyContentState _state = _MyContentState();

  @override
  State<StatefulWidget> createState() {
    return _state;
  }
}

class _MyContentState extends State<_MyContent> with AutomaticKeepAliveClientMixin {

  List<String> items = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: RefreshIndicator(
        child: ListView.builder(
          itemBuilder: (context, index) {
            if (index == items.length) {
              _load();
              return Center(child: CircularProgressIndicator());
            } else if (items.length < index) {
              return null;
            } else {
              return Container(
                child: ListTile(title: Text(items[index])),
              );
            }
          },
        ),
        onRefresh: refresh,
      )
    );
  }

  void _load() async {
    await new Future.delayed(const Duration(seconds: 1));
    setState(() {
      var s = items.length;
      for (var i = 0; i < 20; i++) {
        items.add("アイテム${s + i}");
      }
    });
  }

  Future<void> refresh() {
    return Future.sync(() {
      setState(() {
        items.clear();
        _load();
      });
    });
  }

  @override
  bool get wantKeepAlive => true;
}

以上です

AppStore公開用のスクリーンショットの作り方

はじめに

iPhoneアプリをAppStoreに公開するときスクリーンショットを提出する必要がある。やる機会も大してないのでいざやろうとすると毎回やり方調べてるのでメモしておく。

最低限必要な画像のサイズは以下を1枚ずつ(2019/07/18時点)
iPhone6.5インチ用の1242x2688
iPhone5.5インチ用の1242x2208

手順はざっくり以下
1. iPhoneシュミレータでアプリのスクリーンショットを保存(1枚)
2. AppLaunchPadで1.でとった画像をiPhoneぽい?画像にする
3. Cacooで2.で作った画像にアプリの説明分や見出しをつける

1. iPhoneシュミレータでアプリのスクリーンショットを保存

iPhoneシュミレータでキャプチャしたい状態で「Cmd + s」でスクリーンショットを保存

2. AppLaunchPadで1.でとった画像をiPhoneぽい?画像にする

https://theapplaunchpad.com/
こちらのサイトを使う

画像をアップロードして「Preview and Export」すると各インチの画像がダウンロードできる。この中の5.5インチと6.5インチを使う。

3. Cacooで2.で作った画像にアプリの説明分や見出しをつける

https://cacoo.com/
みんな大好きCacoo

画面下のサイズを「任意のサイズ」を指定して、1242x2688をセット。キャンバスができるのでそこに2.で作った画像を挿入。無料プランだと512K以上の画像をアップできないので、大きい場合はMacの「プレビュー」アプリでリサイズしてからアップする。あとは、テキストを挿入にして文字色や背景色も適当につける
画像が完成したら画面左の「エクスポート」ボタンでダウンロードできる。

画像つきでメモっとかないとまたわからなくなるかな、、以上です

flutter ListViewで無限スクロール

これが答えだったのかあ
https://qiita.com/najeira/items/454462c794c35b3b600a

よくわからず FutureBuilder 使わないといけないのかなとか色々試行錯誤してたらこんなバッチリな記事がありました。ListView作るときってitemCount必須かと思っていたのだが省略できるのか、、

参考記事からhttp通信なくしてListViewで無限スクロールの部分だけ抜き出した。たぶん1番簡単な無限スクロールのコード

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: HomeScreen(),
  ));
}

class HomeScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HomeScreenState();
  }
}

class HomeScreenState extends State<HomeScreen> {

  List<String> items = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("無限スクロール")),
      body: ListView.builder(
        itemBuilder: (context, index) {
          if (index == items.length) {
            _load();
            return Center(child: CircularProgressIndicator());
          } else if (items.length < index) {
            return null;
          } else {
            return Container(
              child: ListTile(title: Text(items[index])),
            );
          }
        },
      ),
    );
  }

  void _load() async {
    await new Future.delayed(const Duration(seconds: 3));
    setState(() {
      var s = items.length;
      for (var i = 0; i < 20; i++) {
        items.add("アイテム${s + i}");
      }
    });
  }
}

以上です

ionic cordova-firebase-pluginとphonegap-plugin-pushを併用する

Badノウハウ
今回やりたかったことはphonegap-plugin-pushを使用してプッシュ通知を実装しているプロジェクトに、firebase analyticsを導入したいということ。新規のプロジェクトだったらプッシュ通知もfirebaseに寄せればいいのだろうが古いプロジェクトなのでそうもいかず

プラグインを入れた状態でxcodeでビルドしようとするとこんなエラーが...

Undefined symbol: _OBJC_CLASS_$_FIRComponent
Undefined symbol: _OBJC_CLASS_$_FIRComponentType
Undefined symbol: _OBJC_CLASS_$_FIRComponentContainer

調べるとLinked Frameworks and LibrariesのところにGoogleUtilities.frameworkが2ついた。けど、それぞれのプラグインのplugin.xmlを見るとGoogleUtilities.frameworkはない。FirebaseMessaging.frameworkが被ってる

cordova-plugin-firebase/plugin.xml

<framework custom="true" src="src/ios/Firebase/Messaging/FirebaseMessaging.framework" />

phonegap-plugin-push/plugin.xml

<framework src="FirebaseMessaging" type="podspec" spec="~> 2.0.0"/>

それぞれのプラグインが同じframeworkの別バージョンを使用しているのが原因ぽい。今回の対応はphonegap-plugin-pushのFirebaseMessagingを削除して使うこととした。すると次は以下のエラー

.../Plugins/phonegap-plugin-push/PushPlugin.m:316:25: Use of undeclared identifier 'FIRApp'
.../Plugins/phonegap-plugin-push/PushPlugin.m:317:26: Use of undeclared identifier 'FIRApp'

前後のソースを見ると

if(isGcmEnabled && [[self fcmSenderId] length] > 0) {
    NSLog(@"Using FCM Notification");
    [self setUsesFCM: YES];
    dispatch_async(dispatch_get_main_queue(), ^{
        if([FIRApp defaultApp] == nil)
            [FIRApp configure];
        [self initRegistration];
    });
} else {
    NSLog(@"Using APNS Notification");
    [self setUsesFCM:NO];
}

となっていて、FIRAppがundeclaredでエラーになっているところはisGcmEnabledがtrueの場合だけ。phonegap-plugin-push使うってことはFCMじゃなくてAPNSのデバイスIDが欲しいはずなのでここは通らないはず。FCMを使わない場合は、GoogleService-Info.plistのIS_GCM_ENABLEDをfalseにするので絶対通らない(はず)。なのでここをコメントアウトした。firebaseのプラグインでAPNSのデバイスID取得できるようにしてくれれば1番キレイな形だとは思うが見た感じそんなメソッドは用意されていなそうだった。

以上です

flutter キーボードに完了ボタンを設置する

https://github.com/diegoveloper/flutter_keyboard_actions
これを使うとできるらしい。サンプルコードはサンプルが多かったのでとりあえず最低限のコードを

f:id:yoppy0066:20190703210442p:plain:h320

// ☆importする
import 'package:keyboard_actions/keyboard_actions.dart';

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Hello")),
      // ☆ FormKeyboardActionsで囲む
      body: FormKeyboardActions(
        child: Content(),
      ),
    );
  }
}

class Content extends StatefulWidget {
  Content({Key key}) : super(key: key);

  @override
  _ContentState createState() => new _ContentState();
}

class _ContentState extends State<Content> {

  var _emailFocusNode = FocusNode();
  var _passwordFocusNode = FocusNode();

  @override
  void initState() {
   // ☆ focustNodeをactionsにセット
    FormKeyboardActions.setKeyboardActions(
      context,
      KeyboardActionsConfig(
      actions: [
        KeyboardAction(focusNode: _emailFocusNode),
        KeyboardAction(focusNode: _passwordFocusNode),
      ],
      )
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () {
          _emailFocusNode.unfocus();
          _passwordFocusNode.unfocus();
        },
        child: Container(
          color: Colors.white,
          child: Padding(
            child: Column(
              children: <Widget>[
                // ☆ focusNodeをセット
                TextField(focusNode: _emailFocusNode),
                TextField(focusNode: _passwordFocusNode),
              ],
            ),
          ),
        )
    );
  }
}

以上です

flutter ios 画面タップでキーボードを閉じる

今回やりたかったことはキーボードが表示されているときに画面をタップしたらキーボードを閉じるということ。swiftでの開発でも考慮する必要があるがflutterでも同じだった

以下のようにすればいいとみんな書いてるがなぜか上手くいかず、、

FocusScope.of(context).requestFocus(FocusNode());

仕方がないので別のやり方

class Content extends StatefulWidget {
  Content({Key key}) : super(key: key);

  @override
  _ContentState createState() => new _ContentState();
}

class _ContentState extends State<Content> {

  var _emailFocusNode = FocusNode();
  var _passwordFocusNode = FocusNode();

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // ココ!!
        _emailFocusNode.unfocus();
        _passwordFocusNode.unfocus();
      },
      child: Container(
        color: Colors.white,
        child: Padding(
          child: Column(
            children: <Widget>[
              TextField(focusNode: _emailFocusNode),
              TextField(focusNode: _passwordFocusNode),
            ],
          ),
        ),
      )
    );
  }
}

TextFieldごとにfocusNodeを用意してセットして、画面がタップされたらunfocusを呼ぶ。requestFocus(FocusNode()) のやり方は本当は動くのかな、、なぜ動かないのかな、、以上です