FlutterとAngularでModelを再利用

2018/04/18 Flutter JP Meetup

@ntaoo

 

モチベーション

同じようなプロダクションコードおよびテストコードを3回も書きたくない!

 

  • Web App : JavaScript

  • iOS App : Swift

  • Android App : Java, Kotlin

ソリューション

すべてDartで書く

  • Web App : Angular (Dart)

  • iOS App : Flutter (Dart)

  • Android App : Flutter (Dart)

  • 共通のプラットフォーム非依存のModel (Dart)に依存

アイデア

Flutter

UI

Angular

UI

Model

 

現実は単純ではない

Flutter

UI

Angular

UI

Model

 

  • アプリによらずモデルがまったく同じとは限らない。

  • 共通化できる部分とアプリ個別の部分がある。

  • 要件による。

環境に依存するAPI

 

  • Web (ブラウザ)特有のもの

    • Window

    • LocalStorage

    • etc...

  • API clients (Browser HTTP Client, Native HTTP Client, etc...) 

  • Google Analytics, Firebase等

 

状態更新方法の差異

 

  • Flutter: SetState(() {});

  • Angular: ChangeDetection

 

再利用性のため、ModelのStateが変更された際の状態更新ロジックをできるだけ合わせたい

 

どうするか

簡単なデモ用アプリを作りました

Hacker News Reader

https://github.com/ntaoo/hacker_news_flutter_angular

  • Flutter App

  • Angular App

  • 共通モデル

DEMO

DI

  • Modelに環境依存オブジェクトをDI
    • コンストラクタインジェクション
    • 共通Interface
  • HTTP Clientの場合
    • package:http
      • Angularの場合:BrowserClient (implements Client)
      • Flutterの場合:IoClient (implements Client)

 

プラットフォーム非依存の新しいDIライブラリ?

状態更新: StreamでPush

Flutter

UI

Angular

UI

Model

 

状態更新: Flutter

StreamBuilder

  Widget _buildResults() {
    return new StreamBuilder(
      stream: hackerNewsService.results,
      builder: (context, snapshot) => new ListView(
            padding: EdgeInsets.fromLTRB(1.0, 20.0, 1.0, 0.0),
            children: (snapshot?.data ?? <Item>[])
                .map<NewsItem>((Item item) => new NewsItem(item))
                .toList(),
          ),
    );
  }

状態更新: Angular

AsyncPipe

<material-input leadingText="Filter" style="width: 100%" #input
 (keyup)="hackerNewsService.filterQuery.add(input.inputText)">
</material-input>

<article *ngFor="let item of hackerNewsService.results | async">
  <item [item]="item"></item>
</article>

共通Model (抜粋)

import 'dart:async';

import 'package:http/http.dart' as http;
import 'dart:convert';

class HackerNewsService {
  final http.Client _client;
  StreamController<String> filterQuery = new StreamController();
  Stream<List<Item>> results = new Stream.empty();

...

  HackerNewsService(this._client) {
...
}

まとめ

  • 環境依存コードはDIする

  • StreamでUIとModelをつなぐ

  • 共通Modelをpackageに切り出す

元ネタ

https://www.youtube.com/watch?v=PLHln7wHgPE