
In letzter Zeit kann man in der mobilen Community oft von Flutter, React Native hören. Ich war interessiert, den Gewinn aus diesen Stücken zu verstehen. Und wie sehr sie das Leben bei der Entwicklung von Anwendungen wirklich verändern werden. Als Ergebnis wurden 4 (vom Standpunkt der ausgeführten Funktionen identisch) Anwendungen erstellt: natives Android, natives iOS, Flutter, React Native. In diesem Artikel habe ich beschrieben, was ich aus meinen Erfahrungen gelernt habe und wie ähnliche Elemente von Anwendungen in den betrachteten Lösungen implementiert sind.
Kommentare: Der Autor des Artikels ist kein professioneller plattformübergreifender Entwickler. Und alles, worüber geschrieben wird, ist das Aussehen eines unerfahrenen Entwicklers für diese Plattformen. Ich denke jedoch, dass diese Überprüfung für Personen nützlich sein wird, die bereits eine der in Betracht gezogenen Lösungen verwenden und Anwendungen für zwei Plattformen schreiben oder den Interaktionsprozess zwischen iOS und Android verbessern möchten.
Als entwickelte Anwendung wurde beschlossen, einen „Sport-Timer“ zu entwickeln, der Sportlern beim Intervalltraining hilft.
Die Anwendung besteht aus 3 Bildschirmen.
Timer-Betriebsbildschirm
Bildschirm "Trainingsverlauf"
Bildschirm Timer-EinstellungenDiese Anwendung ist für mich als Entwickler interessant, da die folgenden Komponenten, die mich interessieren, von ihrer Erstellung betroffen sind:
- Layout
- Benutzerdefinierte Ansicht
- Arbeiten Sie mit UI-Listen
- Multithreading
- Datenbank
- Netzwerk
- Schlüsselwertspeicherung
Es ist wichtig zu beachten, dass wir für Flutter and React Native eine Brücke (einen Kanal) zum nativen Teil der Anwendung erstellen und damit alles implementieren können, was das Betriebssystem bietet. Aber ich habe mich gefragt, welche Frameworks sofort verfügbar sind.
Auswahl von Entwicklungswerkzeugen
Für eine native Anwendung für iOS habe ich die Xcode-Entwicklungsumgebung und die Programmiersprache Swift ausgewählt. Für natives Android - Android Studio und Kotlin. React Native wurde in WebStorm, der Programmiersprache JS, entwickelt. Flattern - Android Studio und Dart.
Eine interessante Tatsache bei der Entwicklung auf Flutter schien mir, dass Sie die Anwendung direkt von Android Studio (der Haupt-IDE für die Android-Entwicklung) auf einem iOS-Gerät ausführen können.

Projektstruktur
Die Strukturen nativer iOS- und Android-Projekte sind sehr ähnlich. Dies ist eine Layoutdatei mit den Erweiterungen .storyboard (iOS) und .xml (Android), den Abhängigkeitsmanagern Podfile (iOS) und Gradle (Android), Quellcodedateien mit den Erweiterungen .swift (iOS) und .kt (Android).
Android-Projektstruktur
IOS-ProjektstrukturDie Strukturen Flutter und React Native enthalten die Ordner Android und iOS, die die üblichen nativen Projekte für Android und iOS enthalten. Flutter und React Native verbinden sich als Bibliothek mit nativen Projekten. Wenn Sie Flutter auf Ihrem iOS-Gerät starten, beginnt die übliche native iOS-Anwendung mit der verbundenen Flutter-Bibliothek. Für React Native und für Android ist alles gleich.
Flutter und React Native enthalten außerdem die Abhängigkeitsmanager package.json (React Native) und pubspec.yaml (Flutter) sowie die Quelldateien mit den Erweiterungen .js (React Native) und .dart (Flutter), die auch das Layout enthalten.
Flatterprojektstruktur
Reagieren Sie auf die native ProjektstrukturLayout
Für natives iOS und Android gibt es visuelle Editoren. Dies vereinfacht die Erstellung von Bildschirmen erheblich.
Visueller Editor für natives Android
Visueller Editor für natives iOSEs gibt keine visuellen Editoren für React Native und Flutter, aber es gibt Unterstützung für die Hot-Reload-Funktion, die die Arbeit mit der Benutzeroberfläche zumindest irgendwie vereinfacht.
Heißer Neustart in Flutter
Hot-Neustart in React NativeUnter Android und iOS wird das Layout in separaten Dateien mit den Erweiterungen .xml bzw. .storybord gespeichert. In React Native und Flutter wird das Layout direkt aus dem Code abgeleitet. Ein wichtiger Punkt bei der Beschreibung der UI-Geschwindigkeit ist, dass Flutter über eigene Rendering-Mechanismen verfügt, mit denen die Entwickler des Frameworks 60 fps versprechen. Und React Native verwendet native UI-Elemente, die mit js erstellt wurden, was zu einer übermäßigen Verschachtelung führt.
In Android und iOS verwenden wir zum Ändern der View-Eigenschaft einen Link aus dem Code. Um beispielsweise die Hintergrundfarbe zu ändern, führen wir Änderungen direkt am Objekt durch. Im Fall von React Native und Flutter gibt es eine andere Philosophie: Wir ändern die Eigenschaften innerhalb des setState-Aufrufs und die Ansicht selbst wird abhängig vom geänderten Status neu gezeichnet.
Beispiele zum Erstellen eines Timer-Bildschirms für jede der ausgewählten Lösungen:
Layout des Timer-Bildschirms unter Android
Layout des Timer-Bildschirms unter iOSLayout des Flatter-Timer-Bildschirms
@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(
Reagieren Sie auf das Layout des nativen Timer-Bildschirms
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> ); }
Benutzerdefinierte Ansicht
Als ich mich mit den Lösungen vertraut machte, war es mir wichtig, dass absolut jede visuelle Komponente erstellt werden konnte. Zeichnen Sie ui auf der Ebene von Quadraten, Kreisen und Pfaden. Zum Beispiel ist eine Timer-Anzeige eine solche Ansicht.

Für natives iOS gab es kein Problem, da es Zugriff auf Layer gibt, auf dem Sie alles zeichnen können, was Sie wollen.
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
Für natives Android können Sie eine Klasse erstellen, die von View erbt. Definieren Sie die onDraw-Methode (Canvas Canvas) neu, in deren Parameter das Canvas-Objekt darauf gezeichnet wird.
@Override protected void onDraw(Canvas canvas) { pathCircleOne = new Path(); pathCircleOne.addArc(rectForCircle, -90, value * 3.6F); canvas.drawPath(pathCircleBackground, paintCircleBackground); }
Für Flutter können Sie eine Klasse erstellen, die von CustomPainter erbt. Und überschreiben Sie die Paint-Methode (Canvas Canvas, Size Size), die im Parameter das Canvas-Objekt übergibt - das ist eine sehr ähnliche Implementierung wie in 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); }
Für React Native wurde keine sofort einsatzbereite Lösung gefunden. Ich denke, dies erklärt sich aus der Tatsache, dass die Ansicht nur auf js beschrieben wird und von nativen UI-Elementen erstellt wird. Sie können jedoch die React-Native-Canvas-Bibliothek verwenden, die den Zugriff auf Canvas ermöglicht.
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); } }
Arbeiten mit UI-Listen

Der Algorithmus für Android, iOS, Flutter - die Lösungen sind sehr ähnlich. Wir müssen angeben, wie viele Elemente in der Liste enthalten sind. Und geben Sie durch die Nummer des Elements die Zelle an, die Sie zeichnen möchten.
IOS verwendet UITableView zum Zeichnen von Listen, in denen Sie DataSource-Methoden implementieren müssen.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return countCell } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return cell }
Für Android verwenden sie RecyclerView, in dessen Adapter wir ähnliche iOS-Methoden implementieren.
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 }
Zum Flattern verwenden sie eine ListView, in der ähnliche Methoden im Builder implementiert sind.
new ListView.builder( itemCount: getCount() * 2, itemBuilder: (BuildContext context, int i) { return new HistoryWidget( Key("a ${models[index].workTime}"), models[index]); }, )
React Native verwendet eine ListView. Die Implementierung ähnelt früheren Lösungen. Es gibt jedoch keinen Verweis auf die Anzahl und Anzahl der Elemente in der Liste. In der DataSource legen wir die Liste der Elemente fest. Und in renderRow implementieren wir die Erstellung einer Zelle in Abhängigkeit davon, welches Element gekommen ist.
<ListView dataSource={this.state.dataSource} renderRow={(data) => <HistoryItem data={data}/>} />
Multithreading, Asynchronität
Als ich anfing, mich mit Multithreading und Asynchronität zu beschäftigen, war ich entsetzt über die Vielfalt der Lösungen. In iOS - dies ist GCD, Operation, in Android - AsyncTask, Loader, Coroutine, in React Native - Promise, Async / Await, in Flutter-Future, Stream. Die Prinzipien einiger Lösungen sind ähnlich, aber die Implementierung ist immer noch unterschiedlich.
Der geliebte Rx kam zur Rettung. Wenn Sie nicht bereits in ihn verliebt sind, rate ich Ihnen, zu studieren. Es ist in allen Lösungen, die ich in der Form betrachte: 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 ) .timeInterval() .take(3) .subscribe( function (x) { console.log('Next: ' + x); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); })
Wie Sie sehen können, sieht der Code in allen vier Sprachen sehr ähnlich aus. Dies erleichtert Ihnen in Zukunft bei Bedarf den Übergang von einer Lösung für die mobile Entwicklung zu einer anderen.
Datenbank
In mobilen Anwendungen ist der Standard die SQLite-Datenbank. In jeder der betrachteten Lösungen wird ein Wrapper für die Arbeit damit geschrieben. Android verwendet normalerweise den ORM-Raum.
Unter iOS Kerndaten. Flutter kann das sqflite-Plugin verwenden.
In React Native - React-Native-SQLite-Speicher. Alle diese Lösungen sind unterschiedlich konzipiert. Und damit die Anwendungen so aussehen, müssen Sie SQLite-Abfragen manuell schreiben, ohne Wrapper zu verwenden.
Es ist wahrscheinlich besser, auf die Realm-Datenspeicherbibliothek zu schauen, die einen eigenen Kernel für die Datenspeicherung verwendet. Es wird unter iOS, Android und React Native unterstützt. Flutter hat derzeit keine Unterstützung, aber die Realm-Ingenieure arbeiten in diese Richtung.
Realm auf Android
RealmResults<Item> item = realm.where(Item.class) .lessThan("id", 2) .findAll();
Realm unter iOS
let item = realm.objects(Item.self).filter("id < 2")
Realm in React Native
let item = realm.objects('Item').filtered('id < 2');
Schlüsselwertspeicherung
Native iOS verwendet UserDefaults. In nativem Android Einstellungen. In React Native und Flutter können Sie Bibliotheken verwenden, die einen Wrapper nativer Schlüsselwertspeicher darstellen (SharedPreference (Android) und 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")
Flattern
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.getInt(my key') prefs.setInt(my key', myValue)
Native reagieren
DefaultPreference.get('my key').then(function(value) {console.log(value)}); DefaultPreference.set('my key', myValue).then(function() {console.log('done')});
Netzwerk
Um mit dem Netzwerk in nativem iOS und Android zu arbeiten, gibt es eine Vielzahl von Lösungen. Am beliebtesten sind Alamofire (iOS) und Retrofit (Android). React Native und Flutter haben ihre eigenen plattformunabhängigen Clients, um online zu gehen. Alle Kunden sind sehr ähnlich gestaltet.
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 …
Flattern
http.Response response = await http.get('https://timerble-8665b.firebaseio.com/messages.json')
Native reagieren
fetch('https://timerble-8665b.firebaseio.com/messages.json') .then((response) => response.json())
Entwicklungszeit

Es ist wahrscheinlich falsch, aufgrund meiner Entwicklungszeit Schlussfolgerungen zu ziehen, da ich ein Android-Entwickler bin. Ich denke jedoch, dass es für iOS-Entwickler einfacher sein wird, in die Flutter- und Android-Technologie einzusteigen als in React Native.
Fazit
Als ich anfing, einen Artikel zu schreiben, wusste ich, was ich zum Schluss schreiben würde. Ich werde Ihnen sagen, welche Lösung mir besser gefallen hat und welche Lösung nicht verwendet werden sollte. Aber dann, nachdem ich mit Leuten gesprochen hatte, die diese Lösungen in der Produktion verwenden, stellte ich fest, dass meine Schlussfolgerungen falsch sind, weil ich alles von der Seite meiner Erfahrung aus betrachte. Hauptsache, ich habe festgestellt, dass es für jede der betrachteten Lösungen Projekte gibt, für die sie ideal geeignet sind. Und manchmal ist es für ein Unternehmen wirklich rentabler, eine plattformübergreifende Anwendung zu erstellen, als die Entwicklung von zwei nativen Anwendungen zu planen. Und wenn eine Lösung für Ihr Projekt nicht geeignet ist, denken Sie nicht, dass sie im Prinzip schlecht ist. Hoffe dieser Artikel ist hilfreich. Danke, dass du bis zum Ende gekommen bist.
In Bezug auf Artikeländerungen schreiben Sie bitte in eine persönliche E-Mail, ich werde alles gerne beheben.