Flutter/DartでBitfinexのレンディングアプリを作る(Part4 バックグラウンド/mbaas/テスト)

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認くださ</b>い</font>
* 10万文字を超える超大作になっています(笑)

---

# はじめに
* この記事は、初めてのスマホアプリを作るまでの軌跡を残したものです。
* この記事では、バックグラウンドプロセスとmBaaSを紹介します
* テストはうまく行かなかったというメモだけです

## 関連記事
* [Flutter/DartでBitfinexのレンディングアプリを作る(Part1 インストールと画面作成/画面遷移)](https://qiita.com/kazutxt/items/e4f3bbb4d8b301f4141c)
* [Flutter/DartでBitfinexのレンディングアプリを作る(Part2 stateful/データの永続化)]
(https://qiita.com/kazutxt/items/c114bb6f6bf6969cfa58)
* [Flutterの"Widget of the Week"がめっちゃ勉強になったので、まとめてみた](https://qiita.com/kazutxt/items/10355961ee2fa3a65023)
* [Flutter/DartでBitfinexのレンディングアプリを作る(Part3 グラフ描画/ファイル分割/設計書/アイコン)](https://qiita.com/kazutxt/items/023b6ef94d095a694a60)

# バックグラウンド実行
* flutterは基本的にシングルスレッドとのことなので、マルチスレッドをするためには、パッケージが必要です。iOSは非対応なので、一旦androidだけを対象とします。

## パッケージのインポート

``` pubspec.yaml
dependencies:
  android_alarm_manager: '0.4.5+13'
```

## mainメソッドの中で呼び出す
* 呼び出す関数は、別のクラスに切り出して「BackGroundTask.checkCondition」メソッドに分けています

``` main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  if (Platform.isAndroid) {
    await AndroidAlarmManager.initialize();
    await AndroidAlarmManager.periodic(
        const Duration(minutes: 5), 0, BackGroundTask.checkCondition);
  }
  runApp(MyApp());
}
```
* WidgetsFlutterBinding.ensureInitialized() は環境を初期化するために使います
    * これを入れないと動く時と、動かない時があるので、安全のために入れておきます
* Platform.isAndroidはOSを判断するフラグです。こちらは「dart:io」に含まれています
* initializeをした上で、定期(5分周期)で呼び出す関数を設定します  

## データ共有
* メモリが分離されているため、メインスレッドと直接データを渡せない点に注意が必要です。
    * shared_preferences , 呼出時の引数にいれて呼び出す分には問題ありません

``` BackGround.dart
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
``` 
* 上記のスレッドが分かれているためか、リロードしないと値が最新化されないことがありました。
* portを通じて通信をすることも可能ですが、今回は利用しませんでした。




#  mBaaS（mobile backend as a Service）
* アプリのバックグラウンドはアプリを終了してしまうと動かないので、バックエンドのサーバが欲しくなりました
* ニフクラという無料サービスがあり、flutterと連携ができるため、気軽に連携できます。
* 先にニフクラのアカウントを作成しておきます

* 参考文献
    * https://blog.mbaas.nifcloud.com/entry/2020/01/14/103404
    * https://qiita.com/sasachi1231/items/2feae7cc4794c0e68fcf
    * https://pub.dev/packages/ncmb
 
``` pubspec.yaml
dependencies:
  ncmb: ^0.1.7
```


## ソースコード

``` NCMBUtil.dart
class NCMBUtil {
  // ncmbの初期化
  static final NCMB ncmb = new NCMB('APPLICATION_KEY', 'CLIENT_KEY');

  static save(String key, String value) async {
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
    String androidID = androidInfo.androidId;

    NCMBObject item = ncmb.Object('Item')
      ..set('id', androidID)
      ..set('key1', 'value1')
      ..set('key2', 'value2');
    await item.save();
    print(item.get('objectId'));
  }
}
```
* 上記ではキーに文字列だけを入れていますが、配列とかも使えます。
* 戻り値として、objectIdが返ってきます

## 結果イメージ
![bfcl-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/fe6c36ea-07d1-f45b-b4f6-840e83972135.png)


# テスト
* flutterにはunitTest/WidgetTest/Integrationテストの3種類があります。

## 単体テスト
* いわゆるシンプルなロジックの確認を行うテスト

``` unit_test.dart
void main() {
  ChartLogic cl = new ChartLogic();
  group('グラフエリアテスト', () {
    test("折れ線グラフが表示されること", () async {
      Widget graphData = await cl.getGraphData();
      expect(graphData is TimeSeriesChart, true);
    });
  });
}
```

* 結果：いまいち
* 原因：そもそも外部APIを叩いてグラフ化するのが主なので、取得→加工→グラフ化を分割していなかった。その結果、最終取得の戻り値がグラフ本体のWidgetになってしまい、テストしづらくなってしまった
    * 加工部分がstaticクラスのライブラリかしていたこともあり、mock化もしずらい。
    * 一方で、ライブラリの方のテストはできた

## Widgetテスト
* 画面のタップやスワイプなどを行い、Widgetの状態の確認を行うテスト

``` widget_test.dart
void main() {
  testWidgets('Info画面を表示する', (WidgetTester tester) async {
    // Build our app and trigger a frame.

    Widget testWidget = new MediaQuery(
        data: new MediaQueryData(), child: new MaterialApp(home: new Info()));
    await tester.pumpWidget(testWidget);
```

* 結果：いまいち。というか動かない
* 原因：MediaQueryのエラーが多発とAPIを叩く部分が原因のようなエラーが多発
    * どちらも回避策が見つからず、後者はライブラリのstaticクラスを使っているため、(インスタンス化しないので)mockによる差し替えも難しく、メソッドの戻り値を丸ごと差し替えるとほぼ意味がない試験になってしまう。。。

## Integrationテスト
* シミュレータを起動して、実動作を確認するテスト

``` integration_test.dart
void main() {
  FlutterDriver driver;

  setUpAll(() async {
    driver = await FlutterDriver.connect();
  });

  tearDownAll(() async {
    if (driver != null) {
      driver.close();
    }
  });
}
```
* 結果；動かない
* 原因：mbaasのncmbのライブラリとバージョンの問題で入らない。ドライバの起動でエラーが起こり、原因がわからない。まぁ勉強不足ですね。

### そもそもの根本的な部分の反省をまとめると

* テスタビリティを意識した実装をしていなかった
    * デザインパターンなどをでメソッド分割が不十分
* アプリそのものがテストしづらい
    * APIを叩いて描画をする関係上、ロジックがあまりないことと、描画に深く関係して、前回やった通りsetStateを使いたいために、ViewとLogicがしっかりと分けきれなかった
*  勉強不足
    * Javaの試験は一通りやってきたので、同じようにできるのかと思ったのですが、画面操作など勝手が違う部分で躓いた。。。

テストは、次のアプリで再トライかな