どうも。@nnsnicoです。
今週末にAndroid関連のLT会があるのでその資料を作る前にどんなことを話そうかを整理する目的でここに話すことを書いておきます。
はじめに
最近(?)なにかと流行っているのではないかという感じのクロスプラットフォームアプリのフレームワーク。わざわざAndroidとiOSで書き分けなくても、1つのプログラミング言語で同じもの作れるなんてすごくない?と思って興味を持ち始めました。
フレームワーク自体はたくさんあるのですがどれがどういった良さ悪さを持っているのかはさっぱりだし、文献がすくねぇ(日本語で書いてあるものはおろか英語も公式コミュニティしか盛り上がってない)からいざ試そうにも試しにくい
今回お話しすることはそんな数あるフレームワークの中でもまあまあよさげあさげなFlutterとReactNativeを使ってUIも機能もほぼ同じのアプリを作ってみた感想をメリットとデメリットを添えてお話しすることで少しでも触ってもらえるきっかけになってもらえればと思います。
全く知らない人向けにお話しするので、両フレームワークの簡単な説明もします。
つくったもの
なるべく実用性があると興味がわきやすいと思ったので、API通信を行ってデータを表示するシンプルなものを作ることを目標にしました。
今回はQiita API v2を使用しました。
主な仕様としては、
- Home画面に新規投稿順で記事がリスト表示される
- 投稿者IDと投稿者のプロフィール画像、記事タイトル、いいねカウント
- リストを押すと記事の詳細へ遷移して記事の内容がマークダウンで表示される
- タイトルは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でつくります。ソースコードは以下にあります。
開発にあたって、@konifarさんのdroidkaigi2018のFlutterアプリを参考にしました。そちらのソースコードも添えて見てみるといいかもしれません。
全部解説するとすごい長文になるので、特徴をかいつまんで説明します。
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
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でつくります。ソースコードは以下にあります。
プロジェクトのメインは/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のフレームワークなだけあって、FlexやCSS(ライクのもの)を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
メリット
デメリット
- Stateful?Stateless?なにそれおいしいの?
- ちゃんとしたアーキテクチャでつくりたいかもしれない
ReactNative
メリット
- React.jsの知識がそのまんま使える!
- XMLライクにViewを書ける!
- Reduxは分かってしまえば最強
- ライブラリ(パッケージ)が豊富
- アプデが盛ん
デメリット
- Reduxを理解するまでに地味に時間がかかる
- たたみかけるように副作用を扱うためのライブラリの仕様も知る必要がある
- Viewが複雑になると途端に見にくくなる(Animationを盛り込むとすごい)
クロスプラットフォーム全体としての感想は、どちらもまだアプリとして安定したパフォーマンスを出せていないことですね。あと一生これがサポートされるのか?という不安もあります。
完全なアプリとして採用するのではなく、プロトタイプとしてさらっと作れることが最大のメリットだと感じました。
他にも色々あると思いますが、ざっくりにはこれぐらいだと思います。
以上、ぐだぐだとそれぞれのアプリを作成した内容を書きましたが、これだけ書いてすぐ思ったのが「ただただ長くてわかりにくいな...」ということでした♨️
資料は作ったらここにあげるかもしれないです。おしまい