Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

DeployGateのコマンドラインツールのインストールでコケた

こんなログが出た

"xcrun clang -o conftest -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/universal-darwin19 -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/backward -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT    -g -Os -pipe -DHAVE_GCC_ATOMIC_BUILTINS conftest.c  -L. -L/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib -L. -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.Internal.sdk/usr/local/lib   -arch x86_64   -lruby.2.6   "
In file included from conftest.c:1:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby.h:33:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: fatal error: 'ruby/config.h' file not found
#include "ruby/config.h"
         ^~~~~~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: note: did not find header 'config.h' in framework 'ruby' (loaded from '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks')
1 error generated.
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

Xcodeコマンドラインツールのパスが違ってたので直したらインストールできた

$ sudo xcode-select -switch /Library/Developer/CommandLineTools

これだけ。

あらざ〜す

MacのhomebrewでOpenSSLがビルドエラーになる場合の対処方法|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

RiverPodをやってみた

Flutterの設計で一番悩ましいのが、ViewとModelの連携。状態遷移だと思われる。

2020年の初頭からFlutterやりはじめて、「Provider + ChangeNotifier」でやってた。これでも全然動く。

ただ、Providerは必ずcontextを経由する必要があるので、ウィジェットの階層構造に気をつけないといけないとか、データを変更した後にnotifyListenersを自分で呼ばないといけないとか、そういう面倒さがあった。

値を再代入するだけで、当該UIのウィジェットが再ビルドされたらそれで良いのだけども、GUI(デスクトップアプリやスマホアプリ)を作る場合にそこが一番面倒。

RIverpodの便利なところは、提供されるProviderがimmutableに設計されているため、グローバルに定義しても良い点。BuildContextに依存しない点の2点がでかい。

riverpod版

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:state_notifier/state_notifier.dart';

final counterProvider = StateNotifierProvider((_) => CounterNotifier());

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier(): super(0);
  void increment () => state++;
}

class CounterPage extends HookWidget {

  Widget build(BuildContext context) {

    final v = useProvider(counterProvider.state);
    final counter = useProvider(counterProvider);

    return Scaffold(
      body: Center(
        child: Text(v.toString(), style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

StatefulWidget + State + setState

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: Center(
        child:  Text('$_counter', style: TextStyle(fontSize: 24))
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        child: new Icon(Icons.add),
      ), 
    );
  }
}

この2つを見比べると、パッと見るだけでも以下のことがわかる。

  • ユーザーの操作に伴いsetStateを書きまくるコードはだいぶ辛い。
  • StateNotifierではstateを再代入するだけに徹する。公開される値がstateしか無いのがミソ。
    • 再代入するとはいえ、StateTの持ってる色んなプロパティの状態をキープしつつイミュータブルにしたいので、freezedパッケージを使うのよね、と。

個人的には、notifyListener()を意識しなくて良いのが好きなので、StateNotifierを推していきたい。

  • スクロールの管理
  • テキストボックスの値の代入
  • 引数ありの画面遷移

この辺がクリア(自分の中で噛み砕ければ)できれば、Riverpodいける。がんばろう。

FlutterでTextFieldに非同期で取得した結果を初期値に入れたい

色々探してやっとわかった..

flutter.institute

initStateに直接awaitって書くことは出来ない。そりゃそうだな。ビルドが終わってから状態を変え、再ビルドするという方法を探していた。

class MyWidget extends StatefulWidget {
    @override
    State createState() => new MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
    var _result;

    @override
    void initState() {
        loadAsyncData().then((result) {
            setState(() {
                _result = result;
            });
        });
    }
    
    @override
    Widget build(BuildContext context) {
        if (_result == null) {
            return new Container();
        }    
       return new TextField(initialValue :result);
    }
}

要約するとこうなる。

  • initStateの中にsetStateを用意して、非同期の結果をメンバ変数にいれる
  • setStateもいつ呼ばれるかわからんが、呼ばれるとbuildがもっかい走るので、resultの状態に応じてビルドするWidgetを変える。

うーん、今回はStateFulWidgetにしたけど、ProviderとStatelessWidgetでも同じことができる気がする... あとでやってみよう。

PrismのDialogServiceでDIしたViewModelが毎回初期化される

PrismのDialogServiceでDIしたViewModelに限って、毎回ViewModelのコンストラクタが呼ばれるという現象がありました。

prism:ViewModelLocator.AutoWireViewModel="True"にしているのにね。この設定にした場合、ViewModelのインスタンスはSingletonになってくれるが、DialogServiceで呼び出す小画面に限りなぜか・・・

  • バージョン: Prism.Wpf 7.2.0.1422

結論から言えば、このコードを書けばSingletonになってくれた。

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
      containerRegistry.RegisterSingleton<OrdeSearchPageViewModel>();
}

RegisterSingleton で、型に対象のVMのクラスを指定するだけだった。Prismの沼かもしれん。

Flutterでカメラで画像を撮って保存したい

まー、ありますわね。そーゆーのがね。

要件としては、写真撮ってメモを追加して何件かリストにしてPOSTだから、取った写真をリスト表示できることが必要。これが意外と難しくてワロタ。

最終的に、こういう風にするしかなさげ。

  • image_pickerプラグインで画像をカメラでとって、
  • path_providerにて、application内のディレクトリに保存してパスを持っておく
  • ギャラリーに移す場合は、gallary_saverでファイルをコピーする。
  • image_gallay_saverはバグがあって動かないようだ。

まとめ

ギャラリーに直接保存してしまうと、保存先のファイルパスが取れない。戻り値がboolになるのは、gallary_saverの仕様。

  Future<bool> takePhoto() async {
    final ImagePicker picker = ImagePicker();
    final PickedFile _image = await picker.getImage(source: ImageSource.camera);
    return GallerySaver.saveImage(_image.path)
  }

true/falseより、保存先のパスが一番欲しいよねw issueに上がってるけど、できないのかな。FlutterのMethodChannel#invokeMethod型のFuture返すから、頑張れば取れる可能性も微レ存。flutterの仕様的に。

github.com

あ、あとこのライブラリでやると、画像がギャラリーに保存できなかった。issueも上がってる。

pub.dev

こんだけのコードなんだけどね。多分MethodChannelの使い方が間違っているのだろう。

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/services.dart';

class ImageGallerySaver {
  static const MethodChannel _channel =
      const MethodChannel('image_gallery_saver');

  /// save image to Gallery
  /// imageBytes can't null
  static Future saveImage(Uint8List imageBytes, {int quality = 80, String name}) async {
    assert(imageBytes != null);
    final result =
    await _channel.invokeMethod('saveImageToGallery', <String, dynamic> {
      'imageBytes': imageBytes,
      'quality': quality,
      'name': name
    });
    return result;
  }

  /// Save the PNG,JPG,JPEG image or video located at [file] to the local device media gallery.
  static Future saveFile(String file) async {
    assert(file != null);
    final result =
    await _channel.invokeMethod('saveFileToGallery', file);
    return result;
  }

}

image_gallery_saver/image_gallery_saver.dart at master · hui-z/image_gallery_saver · GitHub

そんなかんじ。

Flutterのjson_serializableで無限ループになったら

おきまりの flutter pub run build_runner build を叩くと、エンドレスにビルドが走って終わらなくなった・・・。@JsonSerializable アノテーション以外のファイルまで見に行ってる。widget_test.dart を見に行くアホがおるかい。

英語でググってみたところ、このissueがあった。

github.com

どうやら、analyzer というFlutter内部で利用されるライブラリのバグのようで、0.39.170.39.14 にダウングレードしたら、ビルドできました...

ダウングレードするには、pubspec.yamlにこの記述を加筆するだけ。

dependency_overrides:
  analyzer: '0.39.14'

追記 2020.9.17

analyzer 0.40.2 で修正されたようです。めでたしめでたし。

Vue.jsでは親子関係の画面をネストされたルーティングで対応する

router.vuejs.org

例えば、お申し込みフォームが複数の画面に分かれているが、「1つの画面操作」として捉えて、データを管理したい場合等。

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // /user/:id/profile がマッチした時に
          // UserProfile は User の <router-view> 内部で描画されます
          path: 'profile',
          component: UserProfile
        },
        {
          // /user/:id/posts がマッチした時に
          // UserPosts は User の <router-view> 内部で描画されます
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

chilren属性を使うだけ。便利。