ionic Platform.readyについて

ionicのPlatform.readyはネイティブ側で必要な初期化処理が完了したときに発火される。ネイティブの機能を使うプラグインを使用する場合、Platform.readyが終わってないと使用できないというわけだ。console.logも実機で動かす場合はcordovaがラップしてObjectivecでログ出力する処理を置き換えてるはずなのでネイティブの機能と言える(たぶん)。例えば、以下のコード。ionViewWillEnterとionViewDidEnterでプラグインを呼び出そうとしている。

import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {

  constructor(public platform: Platform) {
    platform.ready().then(() => {
      window.hoge_plugin.fuga();
    });
  }

  ionViewWillEnter() {
    window.hoge_plugin.fuga();
  }

  ionViewDidEnter() {
    window.hoge_plugin.fuga();
  }
}

おおよそこちらのコードは問題なく動作するはずだが期待どおりに動作しない場合がある。そう、Platform.readyが完了する前にionViewWillEnterやionViewDidEnterが呼ばれた場合だ。アプリ起動後の最初のページなどにこのような処理を書くとエラーになる場合があるだろう。そこで以下のようにPlatform.readyを使うように修正する

export class Tab1Page {

  constructor(public platform: Platform) {
    platform.ready().then(() => {
      window.hoge_plugin.fuga();
    });
  }

  ionViewWillEnter() {
    this.platform.ready().then(() => {
      window.hoge_plugin.fuga();
    });
  }

  ionViewDidEnter() {
    this.platform.ready().then(() => {
      window.hoge_plugin.fuga();
    });
  }
}

当初私はPlatform.readyはアプリ起動後に1度だけ呼ばれると勘違いしていたのでこのコードの意味がよくわからなかった理解するポイントは、

・Platform.readyが完了済みなら即時実行される
・Platform.readyが未完了ならPlatform.readyが完了してから実行される

といったところだろうか。まとめるとプラグインを呼び出す際はPlatform.readyが完了している前提なのでPlatform.readyで囲んでおけば間違いないのだろう

AngularjsでIME入力を完了しないとng-modelに反映されなくてハマった

またAngularjsとか今さらな話だけど...

https://qiita.com/yaegaki/items/c9cf111ef9d0c541a194
こちらを参考に自分のアプリに試してみたところ、動くところと動かないところが出てきた

こんな感じのコードが動かなかった

<div ng-if="show()">
  <input type="text" ng-mode="input.hoge" jp-input />
</div>

ng-showにしたら動いたよ

<div ng-show="show()">
  <input type="text" ng-mode="input.hoge" jp-input />
</div>

以上です

iOS Share Extensionで保存したuserDefaultsの値が取得できなくてハマったよ

https://qiita.com/KosukeQiita/items/994693da551a7101cc9c
こちらを参考にほぼ同じコードで試していたが上手くいかないこと数時間...

保存した値をuserDefaultsから取得しようとしたらこんなエラーぽいメッセージが出てた

[User Defaults] Couldn't read values in CFPrefsPlistSource<0x2805fd380> (Domain: group.xxxxx.yyyyy.zzzzz, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a contai\
ner is only allowed for System Containers, detaching from cfprefsd

が、どうやら関係なかったXcodeのCapabilitiesから設定するApp GroupsをOnにする設定がメインのアプリの方にだけしかされておらず、Extensionの方に設定されていないのが原因でした。つらい...

ionic(cordova) での動画のトリミング

はじめに

ionic(cordova)で動画を時間でトリミングすることができるUIを実装したくてプラグインを探した。Instagramの動画投稿のようなものをイメージしている。

https://github.com/jbavari/cordova-plugin-video-editor
最初に見つかったのがこちらのプラグイン。READMEによると機能は
・Transcode a video
・Trim a Video (iOS only)
・Create a JPEG thumbnail from a video
で、UIは自前で実装することになりそう

(1)最初にサムネイルを生成してそのキャプチャを横に並べて横スクロール可能なUIの作成
(2)動画はvideoタグで表示して(1)をスクロールするとスクロール位置に合わせてseekさせる
(3)選択した範囲から開始と終了時間を取得してこのプラグインでトリミング(iOSのみだが)

こんなイメージになるだろうか...けっこう大変そう...あと、以前、ローカルのファイルをvideoタグで表示させるときに容量とか大きくなると苦労した記憶が...

cordova-plugin-video-trimming-editor プラグイン

https://github.com/nrikiji/cordova-plugin-video-trimming-editor
やりたいことはまさにこれでした

f:id:yoppy0066:20190820113034p:plain

// フォトライブラリから動画ファイルを選択
camera.getPicture(function(path) {
  // 選択された動画ファイルのパスをcordova-plugin-video-trimming-editorに渡す
  VideoTrimmingEditor.open({
    input_path:	path,
    video_max_time: 10, // 範囲指定可能な最大秒数
  }, function(result) {
    console.log(result); // { output_path: "/path/to/zzz.mp4" }
  }, function(error) {
  }
);
}, function(error) {
}, {
  sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
  mediaType: Camera.MediaType.VIDEO
});

以上です

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;
}

以上です