2019年12月4日水曜日

DartでThrowできるもの

DartのAdvent Calendarがあまりにも過疎っていたので、何でもいいから書こうかという気持ちになりました。
そこでLanguage tourを見直しました。

Exceptionのところをみると
NullでないObjectは何でも投げられる!
ということで、いろいろThrowしてみました。

まずはint
  try {
    throw 1;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: 1
投げられました!

いくつか続けて試します。boolとsymbolsとclassです。
  printDivider();
  try {
    throw false;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    int i = 0;
    throw #i;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    var h = Hoge(0);
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
-----------------
What is thrown: false
-----------------
What is thrown: Symbol("i")
-----------------
What is thrown: Instance of 'Hoge'
投げてもいいんですね。 

ふとclassにinstanceを渡さなかったらどうなるのかなと思い試してみました。
 try {
    Hoge h;
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
行けましたね。コンパイルエラーとかにならないですね。
non-nullオブジェクトって言ってた気がしますがいけました。

じゃあ、null自体は?
  try {
    throw null;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
これも、いけます。

ほんとになんでも投げられますね。
関数は。。。
試してみました。
 try {
    var f = (int i) {
      return i + 1;
    };
    print(f(1));
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

結果は
2
What is thrown: Closure 'main_closure'
3

catchした後にも関数として利用できるのは面白いです。何に使うんだって話ですが。

ちなみに、
 try {
    var f = (int i) {
      return i + 1;
    };
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

このように、tryのなかのprintを消してみたら、DartPadでは、結果は
What is thrown: Closure 'main_closure0'
Uncaught TypeError: i.$add is not a functionError: TypeError: i.$add is not a function
のようになってしまいました。

Flutter上で実行したら

I/flutter ( 7570): What is thrown: Closure: (int) => int
I/flutter ( 7570): 3
といった感じで成功したので、Platformによって挙動がちょっと違うみたいです。
詳しい人いたら教えて下さい。

サンプルは下にあります。
https://dartpad.dartlang.org/29bb6a884331c48abb9091a7f3c9941b

Flutter(Android)で実行した場合のログは以下のような感じです。
https://gist.github.com/matsuhiro/8700addb5939dc33be3433d055a96bc3

でも、ここでの内容は、使うことは無いと思います。
ドキュメントにも、こう書かれていますし。

1人 Flutter Advent Calendar 2019:#4 Flutter for Web


現状はまだ正式サポートじゃないです。
ただ、動かすことは可能なので、試してみます。

dev channelに変更してWebアプリを作成します。
作成の仕方はhttps://flutter.dev/docs/get-started/web とか見るのが早いのでやってみてください。コマンド何個か打つだけですし。
簡単に作成出来ます。

https://github.com/matsuhiro/flutter_for_web_test
サンプルおいておきます。

これを実行すると画面幅が十分ならば以下のようになります。

ただ、ブラウザってすごく小さくすることもできます。そうすると

こんなかんじになって、上の段は横幅が小さくなると右端の四角が隠れていると思います。

別にWebに限ったことではないですが、こういうことが起こるのでPositionedDirectionalとかは基本的に使わないほうが良いかなと思います。
Webの場合はユーザーが画面幅を自由に変えられるので、こういった問題が起こりやすいかなと思います。
Flutterのコードで横幅がXXXpx以下だったらこのレイアウト。とか、画面幅ごとにレイアウトの場所の定義テーブルみたいなのを持つのはあまりやりたくは無いですからね。。。

SnackBarもちゃんとブラウザで表示されて、UIに関して言えば、それなりに動くなーという印象です。

しかし、公式にもありますが、
https://flutter.dev/docs/development/platform-integration/web
dart:ioがつかえなかったりとまだまだな感じです。

なんとなくですけど、1年位で商用で利用できるようになることはないかなーという印象です。


Webとは直接関係ないですが、dev channelでは、以下のことがあったのでついでに記載しておきます。

stable channelはAndroid support libraryを利用していますが、dev channelはAndroidXです。
そのため、既存のProjectでflutter create .とか実行するとAndroidXに対応していないとかでメッセージが出ると思います。

他にはPluginの呼び出しをrunAppの前に行うと

https://gist.github.com/matsuhiro/1ccbc9a6a5527b2c490ed31b79ffd1eb

このようなエラーが表示されます。
dev channelだけです。

Pluginの機能を呼び出す前にWidgetsFlutterBinding.ensureInitialized()を実行せよと言っているわけです。
呼び出せばいいだけといえばいいだけですが、onRunApp的なinterfaceがFlutter側にあっても良いのかなとかは思いました。
アプリの起動シーケンスの中で、Native側の初期化をしたいとはあると思うので。

サンプルを試すときは
https://github.com/matsuhiro/flutter_for_web_test/blob/master/lib/main.dart#L6
コメントアウト消してください。

2019年12月3日火曜日

1人 Flutter Advent Calendar 2019:#3 display resolution


前回のソースをQVGAのデバイス実行してみると以下のようになります。

色々とはみ出していてデザインが破綻しています。
ボタンが要素の右の端にあったらもう押すことも出来ないです。

Androidだけではなく、iOSでも画面の解像度にに種類があります。
FlutterではMediaQueryから取得できるsizeはLogical Pixelとされており、Deviceに関係なくだいたい同になるようにいい感じにやってくれます。Androidのdpみたいなもんですね。

しかし、画面自体が小さすぎれば当然、上の画像のように切れてしまいます。

Androidは画像やsizeの定義ファイルを解像度ごとにdirectoryで分けることで、こういった状況に対処します。
https://developer.android.com/training/multiscreen/screendensities?hl=ja

Flutterでも画像に関しては似たような回避策がありそうです。https://flutter.dev/docs/get-started/flutter-for/android-devs#where-do-i-store-my-resolution-dependent-image-files

しかし、widthやheightなどの値については回避策が無さそうでした。
また、デザインツールで出力されるものはwidhtなどが直値で指定されるので、これですべての解像度に対応するのは難しいです。

とはいえ、デザインツールで作成したものからは直値しかわかりません。
アプローチの方法は、Percentで指定する、全体にScaleをする、FlexBoxを利用してサイズに対して柔軟に対応するかだと思います。

Percent指定ですとflutter_screenutil というのがあります。
最初に画面のサイズを決めて、以降はutilityで提供されている関数でwidth/heightを指定します。
基本的には最初に設定したサイズでscaleしているだけになります。
ただ、これだと全体でScreenUtil.getInstance()を利用してサイズを指定しないとダメなので、ちょっと手間ですし、画面の縦横比に従って設定値を調整しないといけません。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L189-L275

全体にScaleする方法につい
https://flutter.dev/docs/development/ui/widgets/layout
の中から利用できるものを選んで解決しました。

私の見た中ではFittedBoxが解決策に最適でした。
使った場所はこんなかんじです。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L85-L87

全体のレイアウトは崩さずに全体的にScaleされます。
現実には、見栄えのために画面幅に応じたPaddingを入れるのが良いと思います。

これは、最大でも画面幅の90%になるようにPaddingを入れてあります。

これを解像度が大きい端末で実行すると
この様になって、全体にきれいに表示できると思いますし、解決策としても簡単に出来るのではないかと思います。

ここで扱ったサンプルは
https://github.com/matsuhiro/display_resolution_tests
にあります。


FlexBoxに関しては、また違う機会に扱おうと思います。

2019年12月2日月曜日

1人 Flutter Advent Calendar 2019:#2 FigmaとZeplinでUI構築


Flutterでアプリを作成するにあたって、デザインツールでデザインしたものをきれいに取り込みたいという要望とか同期はアプリを作っていれば当然あると思います。
それを実現する方法としてFigma→Zeplin→Flutterの方法を一つの案として照会しようと思います。

連携のためにFigmaとZeplinはそれぞれDesktop版を入れてくれさい。
また、FigmaとZeplinでそれぞれProjectを一つ作っておいてください。

Figmaの細かい説明はしませんがFrameを作成してGoogle Pixel 2 XLを選択してみてください。これでだいたいいい感じに表示されると思います


Group化して、Component化などします。

(画像は適当に拾ったものです。。。)

Component化したGroupを選択した状態で、メニューからExportを選択すると

このようなメニューが出てくるので、Zeplinを選びます。
すると、1回、Browserを経由したあとで、以下のような画面になります。


Figma側では、以下を選んでおきます。


そうすると、Componentに以下のように取り込めます。


ココまでで、Designを取り込む準備が出来ました。

次にZeplinにFlutterのExtensionがあるので入れてみてください。
https://extensions.zeplin.io/johnatagoranet/flutter_extension
これです。

Zeplinを再起動すると以下のようにWidgetのコードが見られます。

これをコピペーすると


画像をassetsディレクトリーに入れて実行すると

となります。うまくいかないときは、Figmaの部品の並び順を見てみてください。z-indexで考えた時に下にあるものほど、下の順番にしないときちんと描画されないです。
Figmaの並び順でFlutterのコードも生成されるので、その順番で重なるためです。

なかなかの再現度ですが、微妙にずれています。
ずれている原因はborderのwidthの計算がextensionとFlutterの実装が合っていないようです。
borderのwidthを0にするとぴったり一致します。Blurなどのeffectは再現できていないようです。

同じようなことは、Sketch → Zeplinでも可能です。

このようにデザイナーが作成したものをきれいに取り込むことが出来るわけですが、小さい解像度のデバイスなどでは以下のようになってしまいます。

これについては、別途記述したいと思います。

ここで用いたcodeは
https://github.com/matsuhiro/figma_zeplin_test
こちらにあります。
Figmaはfigファイルというのを作成できだので、後でgitにでもpushしておきます。
Zeplinのプロジェクトは僕が無料版使っているから共有できないっぽいですね。


2019年12月1日日曜日

1人 Flutter Advent Calendar 2019:#1 snackbar


Advent Calendarに参加してみようかと思いましたが、なんかもう席が埋まっていたので1人でやることにしました。
25日間続けばいいけど。。。

FlutterのSnackbarはAndroidのSnackbarみたいなもんです。
両方ともMaterial Designといっているわけだし
https://material.io/components/snackbars/
の実装って感じです。

Android的なSnackBarだと表示できるものはCharSequenceだけなので、文字しか表示できません。
Spannableとかを利用すればiconを表示したりは出来ると思いますが、そこまでする必要ないだろうなとは思います。
Flutterの場合は、任意のWidgetがcontentとして指定することが出来ます。そのため、大きい画像を入れたり、好きなことが出来ます。

          final snackBar = SnackBar(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20),
                topRight: Radius.circular(20),
              ),
            ),
            behavior: SnackBarBehavior.fixed,
            content: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Image.asset('assets/image_1.jpeg'),
                Text('Yay! A SnackBar!'),
              ],
            ),
            action: SnackBarAction(
              label: 'Undo',
              onPressed: () {
                // Some code to undo the change.
              },
            ),
          );


https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L60-L67


しかしながら、あまり自由なことをしないほうが無難でしょう。SnackBarのソースコードのコメントにも書かれていますし。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/snack_bar.dart#L187-L188

注意することがあるとするとScaffoldのtreeの下にしか存在できないということでしょうか。
Scaffold.of(context).showSnackBar(snackBar)を利用するので、Scaffoldの配下のWidgetから呼び出すことが前提です。

ですが、floatingActionButtonのonPressed内部でshowSnackBarを呼び出そうするのは、誰しもが踏むみたいですね。
解説の記事が至るところにあります。
そういった解説記事を読むのも良いですが、たぶん一番良いのはFlutterが出しているErrorの内容をちゃんとよく読むのが良いです。
エラーの内容に回避策が書かれていますので。

回避策の一例を、いちおう上げておきます。
https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L131-L138

2017年5月22日月曜日

Dagger2を試す


また、だいぶ間が空いてしまった。

個人でツールみたいなアプリ作っていても大規模化することとかは無くて、Dagger2など使う必要など全く無かったりするんですが、そのまま使う必要ないなーと思っているだけなのもつまらないのでちょっと調べてみました。

Dagger2の導入の方法などは公式(https://github.com/google/dagger)を見るのが良いと思います。僕が試したときには最新は2.10でした。

大体の記事がmoduleを作ってcomponentを作って、そこからinjectするのですが、gsonとかアプリ全体で使うやつとrepositoryとか一部のactivityでしか使わなそうやつを分けたいなとか思ったりもするわけでして、moduleを2つに分けたので、componentもそれぞれ作って、2つのcomponentを特定のactivityで利用してinjectしたいなとか思ったので作ってみました。

1:  ├── AppComponent.java  
2:  ├── MainActivity.java  
3:  ├── RepositoryComponent.java  
4:  └── module  
5:    ├── AppModule.java  
6:    └── RepositoryModule.java  

ただ、これコンパイル通りません。
Componentでinject(MainActivity activity)などのmethodがあるとAnnotationProcessorがソースコードをapp/build/generated/source/apt/debug/com/matsuhiro/dagger2sample/MainActivity_MembersInjector.java
に生成します。
inject(MainActivity activity)を持っているComponentが複数あるとそれぞれのComponentがMainActivity_MembersInjector.javaを生成しようとするなどするけど出来ないようです。
複数のComponentをどうやって1つのactivityにinjectしようと調べたりもしたんですが、ActivityComponentみたいに専用のComponent1つ作ればいいかと思いました。
Moduleを複数作って、Componentは、Injectしたいclass毎に作ればよいかと。

でこうしました。

1:  ├── ActivityComponent.java  
2:  ├── MainActivity.java  
3:  └── module  
4:    ├── AppModule.java  
5:    └── RepositoryModule.java  

使い方は、これで良いとしてDagger2をAndroidで試している記事を見るとActivityに何かしらのclassをInjectしていものが多い気がしました。
Dagger2というかDIの場合に、どんなことが嬉しいかっていうと、やはりTestコードを書く時にテストしたいModule以外のModuleを置き換えやすいというのがあると思うのですが、ActivityのメンバーにInjectしたところでTestの時に置き換えやすくない。。。
https://github.com/google/dagger/issues/110#issuecomment-98260225
http://qiita.com/arenahito/items/d9bbca61c8a67cfad226
こんな感じのworkaroundが見つかったりもしますが、ApplicationクラスにComponent持たせて外から差し替えるって、なんだかテストのために無理やりやってる感がすごいなと。まあworkaroundなんでそうなんでしょうけど。

AndroidでDIを使おうと思ったら、ActivityなどAndroid特有の場所以外にロジックを押し込んで、そこでやるのが良さそうです。
そういうので、一番参考になるのってhttps://github.com/android10/Android-CleanArchitecture なわけですが、やっていることがワリといろんなところに飛ぶのと、Testの方がだいぶ素朴なので、自分で最小と思われるものを作ってみました。

ソースコード
1:  public class UseCaseUnitTest {  
2:    final class MockedSettingRepositoryImpl implements SettingRepository {  
3:      @Override  
4:      public String getValue(String key) {  
5:        return "mockrepo";  
6:      }  
7:      @Override  
8:      public void setValue(String key, String value) {}  
9:    }  
10:    @Test  
11:    public void MockedRepository() throws Exception {  
12:      UseCase useCase = new UseCase(new SomeBusinessService(), new MockedSettingRepositoryImpl());  
13:      String message = useCase.doSomething("hoge");  
14:      assertEquals(message, "mockrepo,biz");  
15:    }  
16:    @Test  
17:    public void MockedService() throws Exception {  
18:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
19:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
20:      UseCase useCase = new UseCase(mockedSomeBusinessService, RepositoryModule_ProvideSettingRepositoryFactory.create(new RepositoryModule()).get());  
21:      String message = useCase.doSomething("hoge");  
22:      assertEquals(message, "mockbiz");  
23:    }  
24:    @Test  
25:    public void MockedInput() throws Exception {  
26:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
27:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
28:      UseCase useCase = new UseCase(mockedSomeBusinessService, new MockedSettingRepositoryImpl());  
29:      String message = useCase.doSomething("hoge");  
30:      assertEquals(message, "mockbiz");  
31:    }  
32:    @Test  
33:    public void MockedInput2() throws Exception {  
34:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
35:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
36:      SettingRepository mockedSettingRepository = Mockito.mock(SettingRepository.class);  
37:      Mockito.when(mockedSettingRepository.getValue(Mockito.any(String.class))).thenReturn("mockrepo");  
38:      UseCase useCase = new UseCase(mockedSomeBusinessService, mockedSettingRepository);  
39:      String message = useCase.doSomething("hoge");  
40:      assertEquals(message, "mockbiz");  
41:    }  
42:  }  

テストする時にUseCaseの引数にMockなどを突っ込めばOKなわけです。
MockedRepository()でやっているように引数にinterfaceを持たせる感じにすれば、テスト内で自由に挙動を変更できます。
ただ、めんどくさいですね。Mockito使うのが良いと思います。

MockedService()ではMockito使ってSomeBusinessServiceを差し替えてます。
@Mock使えばよいだけの気もしてます。
ここで、SettingRepositoryのインスタンス化にDagger2が自動生成したコードを使っています。実際にSettingRepositoryがアプリのコードでインスタンス化されるときにはこんな風にされています。
ただ、テストしたいのはUseCaseクラスなので全部Mockにすべきでしょう。
なのでMockedInputとMockedInput2では、違うやり方ですが引数を外部から変更しています。

Dagger2の使い方とか調べてましたが、Activityの中でinject(MainActivity activity)を呼び出したタイミングでactivityのメンバーに対してインスタンスがinjectされるのが自動生成されたコードからわかります。

1:  // Generated by dagger.internal.codegen.ComponentProcessor (https://google.github.io/dagger).  
2:  package com.matsuhiro.dagger2sample;  
3:  import com.matsuhiro.dagger2sample.domain.UseCase;  
4:  import dagger.MembersInjector;  
5:  import javax.inject.Provider;  
6:  public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {  
7:   private final Provider<UseCase> useCaseProvider;  
8:   public MainActivity_MembersInjector(Provider<UseCase> useCaseProvider) {  
9:    assert useCaseProvider != null;  
10:    this.useCaseProvider = useCaseProvider;  
11:   }  
12:   public static MembersInjector<MainActivity> create(Provider<UseCase> useCaseProvider) {  
13:    return new MainActivity_MembersInjector(useCaseProvider);  
14:   }  
15:   @Override  
16:   public void injectMembers(MainActivity instance) {  
17:    if (instance == null) {  
18:     throw new NullPointerException("Cannot inject members into a null reference");  
19:    }  
20:    instance.useCase = useCaseProvider.get();  
21:   }  
22:   public static void injectUseCase(MainActivity instance, Provider<UseCase> useCaseProvider) {  
23:    instance.useCase = useCaseProvider.get();  
24:   }  
25:  }  

具体的には20行目ですね。

onCreate()の一番最初とかで呼び出してあげなとバグになりそうだなとか、何回呼び出しても大丈夫なんだなーとかわかったのは面白かったです。

Dagger2のアプリのコード部分だけみていると魔法のようも思えますが、自動生成されたコードをみると割合と単純な事をやっているもんですね。

bloggerでソースコード書くのめんどくさくなってきたのでqiitaに移ろうかなとか思います。

2016年9月21日水曜日

Multi windowで他アプリを開く


すごい久しぶりに投稿します。
最近ではqiitaに書くのが普通みたいですがブログに書くことにします。

http://qiita.com/takahirom/items/6efc39a7bfe00144ff32
記事の中でも「マルチウインドウ中のActivityからstartActivityForResultで起動されたActivityはマルチウインドウで表示(画面分割された状態で表示)されます。」のように記述されていますが、orientationを固定するなどしておけばmulti windowモードになっても未対応な旨が表示されるだけでmuti windowモードでアプリが起動することはないです。
startActivityForResultの挙動がPreview版だけなのかと気になるところなので実際にアプリを作って試してみました。
作成したアプリ

コードの中身はといえば
https://github.com/matsuhiro/MultiWindowOpener/blob/348744991f94bffb072edbc4472213cd9264a6d4/app/src/main/kotlin/net/matsuhiro/multiwindowopener/MainActivity.kt#L55-L59
のようにstartActivityForResultを利用して起動しているだけです。
これだけで、どんなアプリもmulti windowモードで起動します。

ユーザー的にはブラウザでどこにポケモンがいるのか調べつつpokemonGoで遊ぶとか出来るわけですが、開発者的にはいい迷惑ですね。
アプリ的には画面サイズが変わっているだけの認識のはずですが、完璧に対応しようとしたら画面サイズのパターンがほぼ無限なので最初から工夫しなければ行けないわけですね。

画面サイズの変更時のイベントなど調べたらまた追記しようと思います