(´ワ`)

主に技術系備忘録、たまに日記。

FlutterとReactNativeを両方使った感想をお話しする

どうも。@nnsnicoです。
今週末にAndroid関連のLT会があるのでその資料を作る前にどんなことを話そうかを整理する目的でここに話すことを書いておきます。

はじめに

最近(?)なにかと流行っているのではないかという感じのクロスプラットフォームアプリのフレームワーク。わざわざAndroidiOSで書き分けなくても、1つのプログラミング言語で同じもの作れるなんてすごくない?と思って興味を持ち始めました。
フレームワーク自体はたくさんあるのですがどれがどういった良さ悪さを持っているのかはさっぱりだし、文献がすくねぇ(日本語で書いてあるものはおろか英語も公式コミュニティしか盛り上がってない)からいざ試そうにも試しにくい

今回お話しすることはそんな数あるフレームワークの中でもまあまあよさげあさげなFlutterとReactNativeを使ってUIも機能もほぼ同じのアプリを作ってみた感想をメリットとデメリットを添えてお話しすることで少しでも触ってもらえるきっかけになってもらえればと思います。
全く知らない人向けにお話しするので、両フレームワークの簡単な説明もします。

つくったもの

なるべく実用性があると興味がわきやすいと思ったので、API通信を行ってデータを表示するシンプルなものを作ることを目標にしました。
今回はQiita API v2を使用しました。

主な仕様としては、

  • Home画面に新規投稿順で記事がリスト表示される
  • リストを押すと記事の詳細へ遷移して記事の内容がマークダウンで表示される
    • タイトルはCoordinatorLayoutのようにToolbarが伸びたり縮んだりする

よくある感じのアプリです。

Flutterについて

FlutterはGoogleが開発するクロスプラットフォームで動くフレームワークです。Googleが開発しているだけあって、マテリアルデザインを採用した部品を階層的に組み込んでいくコーディングが特徴です。開発言語は同じくGoogleが開発しているDartという言語です。DartはざっくりいうとJavaみたいなJavaScriptです。詳しくは以下を見てください。

Dart programming language | Dart

Flutter - Beautiful native apps in record time

ReactNative

ReactNativeはfacebookが開発するフレームワークです。もともとはReact.jsというWebのフレームワークだったのですが、それをネイティブ向けに作り直したものです。なので書いているコードはReact.jsです。開発言語はJavaScriptです。
JavaScriptであれば動くのでJavaScriptにクロスコンパイルするaltJSでも基本できます。よくある例だとTypeScriptで開発する人はいたりします。僕もします。いい意味でヤバイ人とかだとScala.jsとかで開発を試みた人もいることを聞いたことがあります。Flutterの開発言語であるDartもaltJSなので動くかもしれないです。React.jsはあるみたいです。活発じゃないみたいですが

React Native · A framework for building native apps using React

https://github.com/cleandart/react-dart

Flutterでつくる

Flutterでつくります。ソースコードは以下にあります。

github.com

開発にあたって、@konifarさんのdroidkaigi2018のFlutterアプリを参考にしました。そちらのソースコードも添えて見てみるといいかもしれません。

github.com

全部解説するとすごい長文になるので、特徴をかいつまんで説明します。
Flutterはレイアウト、ボタン、テキストなどの部品を階層ごとに並べて描画します。また、それらの部品は大きく分けて状態を持つ部品(StatefulWidget)と持たざる部品(StatelessWidget)に分けます。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'QiitaMitsukeTatter',
      theme: themeData,
      home: MyHomePage(title: 'Home'),
    );
  }
}

StatefulWidgetはStatefulWidgetの他に、その状態をStateで持つ必要があります。widget.〜でStatefulWidgetが持つstateを得ることができます。

class MyListItem extends StatefulWidget {
  MyListItem({Key key, @required this.topic}) : super(key: key);

  final Topic topic;  // state

  @override
  _MyListItem createState() => _MyListItem();
}

class _MyListItem extends State<MyListItem> {
  @override
  Widget build(BuildContext context) {
  return Container(
      child: ListTile(
        leading: CircleAvatar(
          backgroundImage: (widget.topic.user.imageUrl != null)
              ? NetworkImage(widget.topic.user.imageUrl)
              : null,
        ),
  ...

また、ThemeDataクラスを各コンポーネントに埋め込むことでそのまんまそのカラーが埋め込まれます

import 'package:flutter/material.dart';

final ThemeData themeData = ThemeData(
  brightness: Brightness.light,
  primaryColor: Colors.lightGreen,
  primaryColorBrightness: Brightness.dark,
  accentColor: Colors.indigoAccent,
  dividerColor: Colors.grey[300],
  backgroundColor: Colors.grey[100],
  secondaryHeaderColor: Colors.white,
);

primariColorとかaccentColorをここで選択できるのが良いですね このような描画される部品たちはカタログとして公式が載せているので大変やさしいです

Material Components Widgets - Flutter

プロジェクトの中も少し解説します。Flutterのプロジェクトは/lib配下にメインのソースコードがあります。ライブラリを追加したいときはpubspec.yaml内のdependenciesにぶち込めばいいです。 ディレクトリの階層も単純です。APIの呼び出しをscreen -> repository -> apiと介して呼び出し、結果のJsonをmodelに入れ、screenでmodelを見て描画するだけとなっています。今回のアプリでは特別なアーキテクチャを使ったわけではありませんが、Reduxなどのサポートもされているようです。
Flutterのすこし不便な点は、部品すべてがマテリアルデザインに凝っちゃうのでiOSマテリアルデザインにしたくない時は採用しにくいです

ReactNativeでつくる

ReactNativeでつくります。ソースコードは以下にあります。

github.com

プロジェクトのメインは/src配下にあります。ライブラリなどはpackage.json内です。 アーキテクチャはRedux + Redux-sagaを使用しました。この辺のお話はしないつもりです。

主要な画面たちはcomponents配下にあります。Androidエンジニアの方々だと、Viewの書き方がXMLライクなので親しみがあるかもしれません。

FlatListでデータをリスト描画しています。

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
  };

  render() {
    const { pressListItem, topics } = this.props.screenProps;
    return (
      <View>
        <StatusBar
          backgroundColor="#689F38FF"
          barStyle="light-content"
        />
        <FlatList
          data={topics}
          extraData={topics}
          keyExtractor={(item, index) => String(index)}
          renderItem={({ item }) => (
            <ListItem
              item={item}
              pressListItem={pressListItem}
            />
          )}
        />
      </View>
    );
  }

もともとがWebのフレームワークなだけあって、FlexCSS(ライクのもの)をViewとして扱うことができます。Androidの場合だと、ライブラリを追加しないと使うことができないのでありがたいです

const styles = StyleSheet.create({
  header: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: '#8BC34AFF',
    overflow: 'hidden',
    justifyContent: 'flex-end',
    alignItems: 'flex-start',
  },
  ...

  fill: {
    flex: 1,
  },
  ...

ReactNativeの不便な点は、Flutterと違って複雑なViewだと見にくいコードになりがちなことです。今回のコードのDetailScreenが一番の例かもしれません。

...
return (
      <View style={styles.fill}>
        <StatusBar
          backgroundColor="#689F38FF"
          barStyle="light-content"
        />
        <ScrollView
          style={[styles.fill]}
          scrollEventThrottle={16}
          onScroll={Animated.event([
            {
              nativeEvent: { contentOffset: { y: this.state.animateHeader } },
            },
          ])}
        >
          <View style={[styles.scrollViewContent, { minHeight: height - Header.HEIGHT - StatusBar.currentHeight }]}>
            <Markdown>
              {topic.body}
            </Markdown>
          </View>
        </ScrollView>
        <Animated.View style={[styles.header, { height: headerHeight }]}>
          <Animated.Text style={[styles.title, { opacity: opacityContent }]}>
            {topic.title}
          </Animated.Text>
          <View style={{ flexDirection: 'row', alignItems: 'flex-end', padding: 8 }}>
            <Animated.Image
              style={[styles.profileImage, {
                opacity: opacityContent,
              }]}
              source={{ uri: topic.user.profile_image_url }}
            />
            <Animated.Text style={[styles.userName, { opacity: opacityContent }]}>
              {topic.user.id}
            </Animated.Text>
          </View>
        </Animated.View>
      </View >
);
...

まとめ

自分なりのFlutterとReactNativeのメリット・デメリットをまとめます

Flutter

メリット
  • マテリアルデザインを意識せず使える!
  • Viewの書き方が階層的でわかりやすい!
  • Javaライクでわかりやすい!
  • Dartのアプデでより見やすい、書きやすいコードに!
デメリット
  • Stateful?Stateless?なにそれおいしいの?
  • ちゃんとしたアーキテクチャでつくりたいかもしれない

ReactNative

メリット
  • React.jsの知識がそのまんま使える!
  • XMLライクにViewを書ける!
  • Reduxは分かってしまえば最強
  • ライブラリ(パッケージ)が豊富
  • アプデが盛ん
デメリット
  • Reduxを理解するまでに地味に時間がかかる
    • たたみかけるように副作用を扱うためのライブラリの仕様も知る必要がある
  • Viewが複雑になると途端に見にくくなる(Animationを盛り込むとすごい)

クロスプラットフォーム全体としての感想は、どちらもまだアプリとして安定したパフォーマンスを出せていないことですね。あと一生これがサポートされるのか?という不安もあります。
完全なアプリとして採用するのではなく、プロトタイプとしてさらっと作れることが最大のメリットだと感じました。

他にも色々あると思いますが、ざっくりにはこれぐらいだと思います。

以上、ぐだぐだとそれぞれのアプリを作成した内容を書きましたが、これだけ書いてすぐ思ったのが「ただただ長くてわかりにくいな...」ということでした♨️

資料は作ったらここにあげるかもしれないです。おしまい