馬が最も耕しましたが、集団農場会長はなりませんでした





最近、モバイルコミュニティでは、Flutter、React Nativeについてよく耳にすることができます。 これらの作品の利益を理解することに興味がありました。 そして、アプリケーションを開発するときに、彼らが実際にどれだけ人生を変えるか。 その結果、ネイティブAndroid、ネイティブiOS、Flutter、React Nativeの4つのアプリケーション(実行された機能の観点から同一)が作成されました。 この記事では、経験から学んだことと、アプリケーションの同様の要素が検討中のソリューションにどのように実装されているかを説明しました。



コメント:この記事の著者はプロのクロスプラットフォーム開発者ではありません。 記述されているのは、これらのプラットフォームの初心者開発者の外観です。 しかし、このレビューは、検討中のソリューションのいずれかを既に使用しており、2つのプラットフォーム用のアプリケーションを作成したり、iOSとAndroid間の対話プロセスを改善したりする人々に役立つと思います。



開発されたアプリケーションとして、インターバルトレーニング中にスポーツに関わる人々を支援する「スポーツタイマー」を作成することが決定されました。



アプリケーションは3つの画面で構成されています。





タイマー操作画面





ワークアウト履歴画面





タイマー設定画面



開発者としてこのアプリケーションに興味があります。なぜなら、興味のある次のコンポーネントがその作成の影響を受けるからです。

-レイアウト

-カスタムビュー

-UIリストの操作

-マルチスレッド

-データベース

-ネットワーク

-キーバリューストレージ



Flutter and React Nativeの場合、アプリケーションのネイティブ部分へのブリッジ(チャネル)を作成し、それを使用してオペレーティングシステムが提供するすべてを実装できることに注意することが重要です。 しかし、私はフレームワークがすぐに使えるものを疑問に思っていました。



開発ツールの選択



iOSのネイティブアプリケーションの場合-Xcode開発環境とSwiftプログラミング言語を選択しました。 ネイティブAndroidの場合-Android StudioおよびKotlin。 JSプログラミング言語であるWebStormで開発されたReact Native。 Flutter-Android StudioおよびDart。



Flutterで開発する際の興味深い事実は、Android Studio(Android開発のメインIDE)からiOSデバイスでアプリケーションを実行できることです。







プロジェクト構造



ネイティブiOSおよびAndroidプロジェクトの構造は非常に似ています。 これは、拡張子.storyboard(iOS)および.xml(Android)、依存関係マネージャーPodfile(iOS)およびGradle(Android)のレイアウトファイル、拡張子.swift(iOS)および.kt(Android)のソースコードファイルです。





Androidプロジェクトの構造





IOSプロジェクトの構造



FlutterおよびReact Native構造には、AndroidおよびiOSフォルダーが含まれています。これらのフォルダーには、AndroidおよびiOSの通常のネイティブプロジェクトが含まれています。 FlutterとReact Nativeは、ライブラリとしてネイティブプロジェクトに接続します。 実際、iOSデバイスでFlutterを起動すると、通常のネイティブiOSアプリケーションはFlutterライブラリが接続された状態で起動します。 React NativeとAndroidの場合、すべて同じです。



FlutterおよびReact Nativeには、依存関係マネージャーpackage.json(React Native)およびpubspec.yaml(Flutter)と、拡張子が.js(React Native)および.dart(Flutter)のソースファイルも含まれています。





フラッタープロジェクトの構造





ネイティブプロジェクト構造の反応



レイアウト



ネイティブiOSおよびAndroidには、ビジュアルエディターがあります。 これにより、画面の作成が大幅に簡素化されます。





ネイティブAndroid用のビジュアルエディター





ネイティブiOS用のビジュアルエディター



React NativeとFlutterのビジュアルエディターはありませんが、ホットリロード機能がサポートされており、少なくとも何らかの形でUIの操作が簡単になります。





Flutterのホットリブート





React Nativeのホットリブート



AndroidおよびiOSでは、レイアウトはそれぞれ拡張子.xmlおよび.storybordを持つ個別のファイルに保存されます。 React NativeおよびFlutterでは、レイアウトはコードから直接派生します。 UIの速度を説明する上で重要な点は、Flutterには独自のレンダリングメカニズムがあり、フレームワークの作成者が60 fpsを約束することです。 また、React Nativeはjsを使用して構築されたネイティブui要素を使用するため、ネストが過剰になります。



AndroidおよびiOSでは、Viewプロパティを変更するには、コードからリンクを使用します。たとえば、背景色を変更するには、オブジェクトに直接変更を加えます。 React NativeとFlutterの場合、別の哲学があります。setState呼び出し内のプロパティを変更し、変更された状態に応じてビュー自体が再描画されます。



選択したソリューションごとにタイマー画面を作成する例:





Androidのタイマー画面のレイアウト





iOSのタイマー画面のレイアウト



フラッタータイマー画面のレイアウト



@override Widget build(BuildContext context) { return Scaffold( body: Stack( children: <Widget>[ new Container( color: color, child: new Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( " ${getTextByType(trainingModel.type)}", style: new TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), new Text( "${trainingModel.timeSec}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 56.0), ), new Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( " ${trainingModel.setCount}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), Text( " ${trainingModel.cycleCount}", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24.0), ), ], ), ], ), padding: const EdgeInsets.all(20.0), ), new Center( child: CustomPaint( painter: MyCustomPainter( //0.0 trainingModel.getPercent()), size: Size.infinite, ), ) ], )); }
      
      





React Nativeタイマー画面のレイアウト



  render() { return ( <View style={{ flex: 20, flexDirection: 'column', justifyContent: 'space-between', alignItems: 'stretch', }}> <View style={{height: 100}}> <Text style={{textAlign: 'center', fontSize: 24}}> {this.state.value.type} </Text> </View> <View style={styles.container}> <CanvasTest data={this.state.value} style={styles.center}/> </View> <View style={{height: 120}}> <View style={{flex: 1}}> <View style={{flex: 1, padding: 20,}}> <Text style={{fontSize: 24}}>  {this.state.value.setCount} </Text> </View> <View style={{flex: 1, padding: 20,}}> <Text style={{textAlign: 'right', fontSize: 24}}>  {this.state.value.cycleCount} </Text> </View> </View> </View> </View> ); }
      
      





カスタムビュー



ソリューションに精通することで、視覚的なコンポーネントを絶対に作成できることが重要でした。 つまり、正方形、円、およびパスのレベルでUIを描画します。 たとえば、タイマーインジケータはそのようなビューです。







必要なものを描画できるLayerにアクセスできるため、ネイティブiOSには問題はありませんでした。



  let shapeLayer = CAShapeLayer() var angle = (-Double.pi / 2 - 0.000001 + (Double.pi * 2) * percent) let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 100), radius: CGFloat(95), startAngle: CGFloat(-Double.pi / 2), endAngle: CGFloat(angle), clockwise: true) shapeLayer.path = circlePath.cgPa
      
      





ネイティブAndroidの場合、Viewを継承するクラスを作成できます。 そして、Canvasオブジェクトが描画されるパラメーターでonDraw(Canvas canvas)メソッドを再定義します。



  @Override protected void onDraw(Canvas canvas) { pathCircleOne = new Path(); pathCircleOne.addArc(rectForCircle, -90, value * 3.6F); canvas.drawPath(pathCircleBackground, paintCircleBackground); }
      
      





Flutterの場合、CustomPainterを継承するクラスを作成できます。 そして、ペイント(Canvasキャンバス、サイズサイズ)メソッドをオーバーライドします。このメソッドは、パラメーターでCanvasオブジェクトを渡します。つまり、Androidと非常によく似た実装です。



  @override void paint(Canvas canvas, Size size) { Path path = Path() ..addArc( Rect.fromCircle( radius: size.width / 3.0, center: Offset(size.width / 2, size.height / 2), ), -pi * 2 / 4, pi * 2 * _percent / 100); canvas.drawPath(path, paint); }
      
      





React Nativeの場合、すぐに使用できるソリューションは見つかりませんでした。 これは、ビューがjsでのみ記述されるという事実によって説明されると思いますが、それはネイティブのui要素によって構築されます。 ただし、canvasへのアクセスを提供するreact-native-canvasライブラリを使用できます。



  handleCanvas = (canvas) => { if (canvas) { var modelTimer = this.state.value; const context = canvas.getContext('2d'); context.arc(75, 75, 70, -Math.PI / 2, -Math.PI / 2 - 0.000001 - (Math.PI * 2) * (modelTimer.timeSec / modelTimer.maxValue), false); } }
      
      





UIリストの使用







Android、iOS、Flutterのアルゴリズム-ソリューションは非常に似ています。 リストに含まれる要素の数を示す必要があります。 そして、要素の番号で、描画したいセルを与えます。

IOSはUITableViewを使用してリストを描画します。リストにはDataSourceメソッドを実装する必要があります。



  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return countCell } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return cell }
      
      





Androidの場合は、RecyclerViewを使用します。このアダプターでは、同様のiOSメソッドを実装しています。



 class MyAdapter(private val myDataset: Array<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.textView.text = myDataset[position] } override fun getItemCount() = myDataset.size }
      
      





フラッターの場合、リストビューを使用します。リストビューでは、同様のメソッドがビルダーに実装されています。



 new ListView.builder( itemCount: getCount() * 2, itemBuilder: (BuildContext context, int i) { return new HistoryWidget( Key("a ${models[index].workTime}"), models[index]); }, )
      
      





React NativeはListViewを使用します。 実装は以前のソリューションに似ています。 ただし、リスト内の要素の数と数への参照はありません。DataSourceで要素のリストを設定します。 そしてrenderRowでは、どの要素が来たかに応じてセルの作成を実装します。



 <ListView dataSource={this.state.dataSource} renderRow={(data) => <HistoryItem data={data}/>} />
      
      





マルチスレッド、非同期



マルチスレッド、非同期に対処し始めたとき、私はさまざまなソリューションに恐怖を覚えました。 iOSでは-これはGCD、Operation、AndroidではAsyncTask、Loader、Coroutine、React Nativeでは-Promise、Async / Await、Flutter-Future、Streamです。 一部のソリューションの原則は似ていますが、実装はまだ異なります。

最愛のRxは救助に来ました。 まだ彼に恋をしていないのなら、勉強することをお勧めします。 RxDart、RxJava、RxJs、RxSwiftの形式で検討するすべてのソリューションに含まれています。



Rxjava



  Observable.interval(1, TimeUnit.SECONDS) .subscribe(object : Subscriber<Long>() { fun onCompleted() { println("onCompleted") } fun onError(e: Throwable) { println("onError -> " + e.message) } fun onNext(l: Long?) { println("onNext -> " + l!!) } })
      
      





Rxswift



 Observable<Int>.interval(1.0, scheduler: MainScheduler.instance) .subscribe(onNext: { print($0) })
      
      





Rxdart



  Stream.fromIterable([1, 2, 3]) .transform(new IntervalStreamTransformer(seconds: 1)) .listen((i) => print("$i sec");
      
      





Rxjs



 Rx.Observable .interval(500 /* ms */) .timeInterval() .take(3) .subscribe( function (x) { console.log('Next: ' + x); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); })
      
      





ご覧のとおり、コードは4つの言語すべてで非常によく似ています。 将来、必要に応じて、モバイル開発用のソリューションから別のソリューションへの移行が容易になります。



データベース



モバイルアプリケーションの標準は、SQLiteデータベースです。 考慮される各ソリューションでは、それを操作するためのラッパーが作成されます。 Androidは通常ORMルームを使用します。



iOSでは、コアデータ。 Flutterはsqfliteプラグインを使用できます。



React Nativeで-react-native-sqlite-storage。 これらのソリューションはすべて異なる設計になっています。 また、アプリケーションを次のように表示するには、ラッパーを使用せずにSqliteクエリを手動で記述する必要があります。



データストレージ用の独自のカーネルを使用するデータストレージ用のRealmライブラリに目を向けた方がよいでしょう。 iOS、Android、React Nativeでサポートされています。 現在、Flutterにはサポートがありませんが、Realmのエンジニアはその方向に取り組んでいます。



Androidのレルム



 RealmResults<Item> item = realm.where(Item.class) .lessThan("id", 2) .findAll();
      
      





iOSのレルム



 let item = realm.objects(Item.self).filter("id < 2")
      
      





React Nativeのレルム



 let item = realm.objects('Item').filtered('id < 2');
      
      





キーバリューストレージ



ネイティブiOSはUserDefaultsを使用します。 ネイティブAndroidでは、設定。 React NativeおよびFlutterでは、ネイティブのキーと値のストア(SharedPreference(Android)およびUserDefaults(iOS))のラッパーであるライブラリを使用できます。



Android



 SharedPreferences sPref = getPreferences(MODE_PRIVATE); Editor ed = sPref.edit(); ed.putString("my key'", myValue); ed.commit();
      
      





iOS



 let defaults = UserDefaults.standard defaults.integer(forKey: "my key'") defaults.set(myValue, forKey: "my key")
      
      





フラッター



 SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.getInt(my key') prefs.setInt(my key', myValue)
      
      





ネイティブに反応する



 DefaultPreference.get('my key').then(function(value) {console.log(value)}); DefaultPreference.set('my key', myValue).then(function() {console.log('done')});
      
      





ネットワーク



ネイティブiOSおよびAndroidでネットワークを操作するには、膨大な数のソリューションがあります。 最も人気があるのは、Alamofire(iOS)とRetrofit(Android)です。 React NativeとFlutterには、プラットフォームに依存しない独自のオンラインクライアントがあります。 すべての顧客は非常によく設計されています。



Android



 Retrofit.Builder() .baseUrl("https://timerble-8665b.firebaseio.com") .build() @GET("/messages.json") fun getData(): Observable<Map<String,RealtimeModel>>
      
      





iOS



 let url = URL(string: "https://timerble-8665b.firebaseio.com/messages.json") Alamofire.request(url, method: .get) .responseJSON { response in …
      
      





フラッター



 http.Response response = await http.get('https://timerble-8665b.firebaseio.com/messages.json')
      
      





ネイティブに反応する



 fetch('https://timerble-8665b.firebaseio.com/messages.json') .then((response) => response.json())
      
      





開発時間







私はAndroid開発者なので、開発時間に基づいて結論を出すのはおそらく間違っています。 しかし、iOS開発者にとっては、FlutterとAndroidを入力するのはReact Nativeを使用するよりも簡単だと思います。



おわりに



記事を書き始めて、私は結論で何を書くかを知っていました。 どのソリューションがより気に入ったのか、どのソリューションを使用すべきではないのかを説明します。 しかし、その後、本番環境でこれらのソリューションを使用する人々と話をした後、私は自分の経験の側面からすべてを見ているため、私の結論が間違っていることに気付きました。 主なことは、考慮されたソリューションのそれぞれについて、それが理想的に適している独自のプロジェクトがあることを認識しました。 また、クロスプラットフォームアプリケーションを作成する方がビジネスにとって有益であり、2つのネイティブアプリケーションの開発をoverしないほうがよい場合もあります。 また、特定のソリューションがプロジェクトに特に適していない場合、原則として悪いと考えるべきではありません。 この記事がお役に立てば幸いです。 最後までありがとう。



記事の編集に関しては、個人的なメールに書いてください。喜んですべてを修正します。



All Articles