
Baru-baru ini, di komunitas seluler, orang sering dapat mendengar tentang Flutter, React Native. Saya tertarik untuk memahami untung dari karya-karya ini. Dan betapa mereka benar-benar akan mengubah kehidupan saat mengembangkan aplikasi. Akibatnya, 4 aplikasi (identik dari sudut pandang fungsi yang dilakukan) dibuat: Android asli, iOS asli, Flutter, React Native. Dalam artikel ini, saya menjelaskan apa yang saya pelajari dari pengalaman saya dan bagaimana elemen aplikasi yang serupa diimplementasikan dalam solusi yang sedang dipertimbangkan.
Komentar: penulis artikel ini bukanlah pengembang lintas platform profesional. Dan semua yang ditulis tentang adalah tampilan pengembang pemula untuk platform ini. Tapi saya pikir review ini akan bermanfaat bagi orang-orang yang sudah menggunakan salah satu solusi yang sedang dipertimbangkan, dan yang ingin menulis aplikasi untuk dua platform atau meningkatkan proses interaksi antara iOS dan Android.
Sebagai aplikasi yang dikembangkan, diputuskan untuk membuat "Timer Olahraga", yang akan membantu orang yang terlibat dalam olahraga saat melakukan latihan interval.
Aplikasi ini terdiri dari 3 layar.
Layar Pengoperasian Timer
Layar Riwayat Latihan
Layar Pengaturan TimerAplikasi ini menarik bagi saya sebagai pengembang, karena komponen berikut yang menarik minat saya akan terpengaruh oleh pembuatannya:
- Tata ruang
- Tampilan Kustom
- Bekerja dengan daftar UI
- multithreading
- Basis data
- Jaringan
- penyimpanan nilai kunci
Penting untuk dicatat bahwa untuk Flutter dan React Native kita dapat membuat jembatan (saluran) ke bagian asli aplikasi dan menggunakannya untuk mengimplementasikan semua yang disediakan oleh sistem operasi. Tapi saya bertanya-tanya kerangka apa yang keluar dari kotak.
Pemilihan alat pengembangan
Untuk aplikasi asli untuk iOS - Saya memilih lingkungan pengembangan Xcode dan bahasa pemrograman Swift. Untuk Android asli - Android Studio dan Kotlin. Bereaksi Asli dikembangkan di WebStorm, bahasa pemrograman JS. Flutter - Android Studio dan Dart.
Fakta menarik ketika mengembangkan Flutter bagi saya sepertinya langsung dari Android Studio (IDE utama untuk pengembangan Android) Anda dapat menjalankan aplikasi pada perangkat iOS.

Struktur proyek
Struktur proyek asli iOS dan Android sangat mirip. Ini adalah file tata letak dengan ekstensi .storyboard (iOS) dan .xml (Android), manajer dependensi Podfile (iOS) dan Gradle (Android), file kode sumber dengan ekstensi .swift (iOS) dan .kt (Android).
Struktur proyek Android
Struktur proyek IOSStruktur Flutter dan React Native berisi folder Android dan iOS, yang berisi proyek-proyek asli yang biasa untuk Android dan iOS. Berkibar dan Bereaksi Asli terhubung ke proyek asli sebagai perpustakaan. Bahkan, ketika Anda meluncurkan Flutter pada perangkat iOS Anda, aplikasi iOS asli yang biasa diluncurkan dengan pustaka Flutter terhubung. Untuk Bereaksi Asli dan untuk Android, semuanya sama.
Flutter dan React Native juga berisi package.json (React Native) dan manajer dependensi pubspec.yaml (Flutter) dan sumber file dengan ekstensi .js (React Native) dan .dart (Flutter), yang juga berisi tata letak.
Struktur Proyek Flutter
Bereaksi Struktur Proyek AsliTata letak
Untuk iOS asli dan Android, ada editor visual. Ini sangat menyederhanakan pembuatan layar.
Editor visual untuk Android asli
Editor visual untuk iOS asliTidak ada editor visual untuk React Native dan Flutter, tetapi ada dukungan untuk fungsi hot reload, yang setidaknya entah bagaimana menyederhanakan pekerjaan dengan UI.
Hot reboot dalam Flutter
Hot reboot dalam React NativeDi Android dan iOS, tata letak disimpan dalam file terpisah dengan ekstensi .xml dan .storybord, masing-masing. Dalam Bereaksi Asli dan Bergetar, tata letak diturunkan langsung dari kode. Poin penting dalam menggambarkan kecepatan ui harus dicatat bahwa Flutter memiliki mekanisme rendernya sendiri, yang dengannya pembuat kerangka menjanjikan 60 fps. Dan Bereaksi Asli menggunakan elemen ui asli yang dibangun menggunakan js, yang mengarah ke sarang berlebih mereka.
Di Android dan iOS, untuk mengubah properti View, kami menggunakan tautan ke sana dari kode dan, misalnya, untuk mengubah warna latar belakang, kami menyebabkan perubahan pada objek secara langsung. Dalam kasus React Native and Flutter, ada filosofi lain: kami mengubah properti di dalam panggilan setState, dan tampilan itu sendiri digambar ulang tergantung pada kondisi yang diubah.
Contoh membuat layar pengatur waktu untuk masing-masing solusi yang dipilih:
Layout layar pengatur waktu di Android
Layout layar pengatur waktu di iOSLayout layar pengatur waktu Flutter
@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(
Bereaksi tata letak layar timer asli
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> ); }
Tampilan khusus
Berkenalan dengan solusi, penting bagi saya untuk membuat komponen visual apa pun. Yaitu, gambar ui di tingkat kotak, lingkaran, dan jalur. Misalnya, indikator penghitung waktu adalah tampilan seperti itu.

Tidak ada masalah untuk iOS asli, karena ada akses ke Layer, di mana Anda dapat menggambar apa pun yang Anda inginkan.
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
Untuk Android asli, Anda bisa membuat kelas yang mewarisi dari View. Dan mendefinisikan kembali metode onDraw (Canvas canvas), dalam parameter yang objek Canvas digambar di atasnya.
@Override protected void onDraw(Canvas canvas) { pathCircleOne = new Path(); pathCircleOne.addArc(rectForCircle, -90, value * 3.6F); canvas.drawPath(pathCircleBackground, paintCircleBackground); }
Untuk Flutter, Anda bisa membuat kelas yang mewarisi dari CustomPainter. Dan menimpa metode paint (Canvas canvas, Size size), yang pada parameter melewati objek Canvas - yaitu, implementasi yang sangat mirip seperti di 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); }
Untuk Bereaksi Asli, solusi di luar kotak tidak ditemukan. Saya pikir ini dijelaskan oleh fakta bahwa pandangan hanya dijelaskan pada js, dan ini dibangun oleh elemen ui asli. Tetapi Anda dapat menggunakan perpustakaan react-native-canvas, yang memberikan akses ke kanvas.
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); } }
Bekerja dengan Daftar UI

Algoritma untuk Android, iOS, Flutter - solusinya sangat mirip. Kita perlu menunjukkan berapa banyak elemen dalam daftar. Dan berikan dengan jumlah elemen sel yang ingin Anda gambar.
IOS menggunakan UITableView untuk menggambar daftar, di mana Anda perlu menerapkan metode DataSource.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return countCell } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return cell }
Untuk Android, mereka menggunakan RecyclerView, di adaptor yang kami menerapkan metode iOS serupa.
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 }
Untuk flutter, mereka menggunakan ListView, di mana metode serupa diimplementasikan dalam pembangun.
new ListView.builder( itemCount: getCount() * 2, itemBuilder: (BuildContext context, int i) { return new HistoryWidget( Key("a ${models[index].workTime}"), models[index]); }, )
Bereaksi Asli menggunakan ListView. Implementasinya mirip dengan solusi sebelumnya. Tetapi tidak ada referensi ke jumlah dan jumlah elemen dalam daftar, di DataSource kami mengatur daftar elemen. Dan di renderRow, kami menerapkan pembuatan sel tergantung pada elemen mana yang datang.
<ListView dataSource={this.state.dataSource} renderRow={(data) => <HistoryItem data={data}/>} />
Multithreading, asynchrony
Ketika saya mulai berurusan dengan multithreading, asynchrony, saya ngeri dengan berbagai solusi. Di iOS - ini adalah GCD, Operation, di Android - AsyncTask, Loader, Coroutine, di React Native - Promise, Async / Await, di Flutter-Future, Stream. Prinsip-prinsip beberapa solusi serupa, tetapi implementasinya masih berbeda.
Rx tercinta datang untuk menyelamatkan. Jika Anda belum jatuh cinta padanya, saya sarankan Anda untuk belajar. Ada dalam semua solusi yang saya pertimbangkan dalam bentuk: 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'); })
Seperti yang Anda lihat, kode ini terlihat sangat mirip di keempat bahasa. Yang mana di masa depan, jika perlu, akan memfasilitasi transisi Anda dari satu solusi untuk pengembangan seluler ke solusi lainnya.
Basis data
Dalam aplikasi mobile, standarnya adalah database SQLite. Dalam setiap solusi yang dipertimbangkan, pembungkus ditulis untuk bekerja dengannya. Android biasanya menggunakan Ruang ORM.
Di iOS, Data Inti. Flutter dapat menggunakan plugin sqflite.
Dalam Bereaksi Asli - react-native-sqlite-storage. Semua solusi ini dirancang secara berbeda. Dan untuk membuat aplikasi terlihat seperti, Anda harus menulis kueri Sqlite secara manual, tanpa menggunakan pembungkus.
Mungkin lebih baik untuk melihat ke perpustakaan penyimpanan data Realm, yang menggunakan kernel sendiri untuk penyimpanan data. Ini didukung di iOS, Android, dan React Native. Flutter saat ini tidak memiliki dukungan, tetapi insinyur Realm bekerja ke arah itu.
Realm di Android
RealmResults<Item> item = realm.where(Item.class) .lessThan("id", 2) .findAll();
Realm di iOS
let item = realm.objects(Item.self).filter("id < 2")
Realm dalam Bereaksi Asli
let item = realm.objects('Item').filtered('id < 2');
Penyimpanan nilai kunci
IOS asli menggunakan UserDefaults. Di Android asli, preferensi. Di Bereaksi Asli dan Bergetar, Anda dapat menggunakan perpustakaan yang merupakan pembungkus toko nilai kunci asli (SharedPreference (Android) dan 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")
Bergetar
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.getInt(my key') prefs.setInt(my key', myValue)
Bereaksi asli
DefaultPreference.get('my key').then(function(value) {console.log(value)}); DefaultPreference.set('my key', myValue).then(function() {console.log('done')});
Jaringan
Untuk bekerja dengan jaringan di iOS asli dan Android ada sejumlah besar solusi. Yang paling populer adalah Alamofire (iOS) dan Retrofit (Android). React Native dan Flutter memiliki klien sendiri yang bebas platform untuk online. Semua pelanggan dirancang dengan sangat mirip.
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 …
Bergetar
http.Response response = await http.get('https://timerble-8665b.firebaseio.com/messages.json')
Bereaksi asli
fetch('https://timerble-8665b.firebaseio.com/messages.json') .then((response) => response.json())
Waktu pengembangan

Mungkin salah untuk menarik kesimpulan berdasarkan waktu pengembangan saya, karena saya seorang pengembang Android. Tapi saya pikir untuk pengembang iOS, memasuki Flutter dan teknologi Android akan terasa lebih mudah daripada di React Native.
Kesimpulan
Mulai menulis artikel, saya tahu apa yang akan saya tulis di kesimpulan. Saya akan memberi tahu Anda solusi mana yang lebih saya sukai, solusi mana yang tidak boleh digunakan. Tetapi kemudian, setelah berbicara dengan orang-orang yang menggunakan solusi ini dalam produksi, saya menyadari bahwa kesimpulan saya salah, karena saya melihat semuanya dari sisi pengalaman saya. Hal utama, saya menyadari bahwa untuk setiap solusi yang dipertimbangkan, ada proyek yang sangat cocok. Dan terkadang itu benar-benar lebih menguntungkan bagi bisnis untuk membuat aplikasi lintas-platform, daripada membajak pengembangan dua yang asli. Dan jika beberapa solusi tidak cocok untuk proyek Anda, jangan berpikir bahwa itu pada prinsipnya buruk. Semoga artikel ini bermanfaat. Terima kasih sudah sampai di akhir.
Mengenai pengeditan artikel, silakan menulis dalam email pribadi, saya akan memperbaiki semuanya dengan senang hati.