Tutorial Flutter: Cara Membuat Aplikasi Flutter Pertama Anda

Diterbitkan: 2022-03-11

Apa itu Flutter?

Flutter adalah SDK pengembangan aplikasi seluler Google yang memungkinkan produk Anda menargetkan platform Android dan iOS secara bersamaan, tanpa perlu mempertahankan dua basis kode terpisah. Selain itu, aplikasi yang menggunakan Flutter juga dapat dikompilasi untuk menargetkan sistem operasi Fuchsia Google yang akan datang.

Flutter baru-baru ini mencapai tonggak penting - versi stabil 1.0. Rilisnya berlangsung di London, 5 Desember 2018, di acara Flutter Live. Meskipun masih dapat dianggap sebagai usaha perangkat lunak awal dan berkembang, artikel ini akan fokus pada konsep yang sudah terbukti dan menunjukkan cara mengembangkan aplikasi perpesanan yang berfungsi penuh yang menargetkan kedua platform seluler utama menggunakan Flutter 1.2 dan Firebase.

Seperti yang dapat dilihat dari grafik di bawah ini, Flutter telah mendapatkan banyak pengguna dalam beberapa bulan terakhir. Pada tahun 2018, pangsa pasar Flutter berlipat ganda dan berada di jalur yang tepat untuk melampaui React Native dalam hal kueri penelusuran, oleh karena itu kami memutuskan untuk membuat tutorial Flutter baru.

Bagan yang membandingkan pengguna Flutter dan React dari Juli hingga September 2018.

Catatan: Artikel ini hanya berfokus pada bagian implementasi tertentu. Referensi kode sumber lengkap untuk proyek ini dapat ditemukan di repo GitHub ini.

Prasyarat

Meskipun upaya telah dilakukan untuk memungkinkan pembaca mengikuti dan menyelesaikan proyek ini meskipun ini adalah upaya pertama mereka dalam pengembangan seluler, banyak konsep pengembangan seluler inti yang tidak spesifik Flutter disebutkan dan digunakan tanpa penjelasan mendetail.

Ini dilakukan untuk menyingkat artikel karena salah satu tujuannya adalah agar pembaca menyelesaikan proyek dalam sekali duduk. Terakhir, artikel ini mengasumsikan bahwa Anda sudah menyiapkan lingkungan pengembangan, termasuk plugin Android Studio dan Flutter SDK yang diperlukan.

Penyiapan Firebase

Menyiapkan Firebase adalah satu-satunya hal yang harus kita lakukan secara independen untuk setiap platform. Pertama-tama, pastikan Anda membuat proyek baru di Dasbor Firebase dan menambahkan aplikasi Android dan iOS di ruang kerja yang baru dibuat. Platform akan menghasilkan dua file konfigurasi yang perlu Anda unduh: google-services.json untuk Android dan GoogleService-Info.plist untuk iOS. Sebelum menutup dasbor, pastikan untuk mengaktifkan penyedia autentikasi Firebase dan Google karena kami akan menggunakannya untuk identifikasi pengguna. Untuk melakukannya, pilih item Otentikasi dari menu lalu pilih tab Metode Masuk.

Sekarang Anda dapat menutup dasbor saat penyiapan lainnya berlangsung di basis kode kami. Pertama-tama, kita perlu meletakkan file yang kita unduh di proyek kita. File google-services.json harus ditempatkan di folder $(FLUTTER_PROJECT_ROOT)/android/app dan GoogleService-Info.plist harus ditempatkan di direktori $(FLUTTER_PROJECT_ROOT)/ios/Runner . Selanjutnya, kita perlu benar-benar menyiapkan pustaka Firebase yang akan kita gunakan dalam proyek dan menghubungkannya dengan file konfigurasi. Ini dilakukan dengan menentukan paket Dart (libraries) yang akan kita gunakan dalam file pubspec.yaml proyek kita. Di bagian dependensi file, rekatkan cuplikan berikut:

 flutter_bloc: shared_preferences: firebase_auth: cloud_firestore: google_sign_in: flutter_facebook_login:

Dua yang pertama tidak terkait dengan Firebase tetapi akan sering digunakan dalam proyek. Dua yang terakhir, mudah-mudahan, cukup jelas.

Terakhir, kita perlu mengonfigurasi pengaturan proyek khusus platform yang akan memungkinkan alur otentikasi kita berhasil diselesaikan. Di sisi Android, kita perlu menambahkan plugin Google-services Gradle ke konfigurasi Gradle tingkat proyek kita. Dengan kata lain, kita perlu menambahkan item berikut ke daftar ketergantungan di file $(FLUTTER_PROJECT_ROOT)/android/build.gradle :

 classpath 'com.google.gms:google-services:4.2.0' // change 4.2.0 to the latest version

Kemudian kita perlu menerapkan plugin itu dengan menambahkan baris ini ke akhir $(FLUTTER_PROJECT_ROOT)/android/app/build.gradle :

 apply plugin: 'com.google.gms.google-services'

Hal terakhir untuk platform ini adalah mendaftarkan parameter aplikasi Facebook Anda. Apa yang kami cari di sini adalah mengedit dua file ini - $(FLUTTER_PROJECT_ROOT)/android/app/src/main/AndroidManifest.xml dan $(FLUTTER_PROJECT_ROOT)/android/app/src/main/res/values/strings.xml :

 <!-- AndroidManifest.xml --> <manifest xmlns:androcom.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/> <activity android:name="com.facebook.FacebookActivity" android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" android:label="@string/app_name" /> <activity android:name="com.facebook.CustomTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="@string/fb_login_protocol_scheme" /> </intent-filter> </activity> <!-- … --> </application> </manifest> <!-- strings.xml --> <resources> <string name="app_name">Toptal Chat</string> <string name="facebook_app_id">${YOUR_FACEBOOK_APP_ID}</string> <string name="fb_login_protocol_scheme">${YOUR_FACEBOOK_URL}</string> </resources>

Sekarang saatnya untuk iOS. Untungnya, kita hanya perlu mengubah satu file dalam kasus ini. Tambahkan nilai berikut (perhatikan bahwa item CFBundleURLTypes mungkin sudah ada dalam daftar; dalam hal ini, Anda perlu menambahkan item ini ke larik yang ada alih-alih mendeklarasikannya lagi) ke $(FLUTTER_PROJECT)ROOT/ios/Runner/Info.plist mengajukan:

 <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>${YOUR_FACEBOOK_URL}</string> </array> </dict> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <string>${YOUR_REVERSED_GOOGLE_WEB_CLIENT_ID}</string> </array> </dict> </array> <key>FacebookAppID</key> <string>${YOUR_FACEBOOK_APP_ID}</string> <key>FacebookDisplayName</key> <string>${YOUR_FACEBOOK_APP_NAME}</string> <key>LSApplicationQueriesSchemes</key> <array> <string>fbapi</string> <string>fb-messenger-share-api</string> <string>fbauth2</string> <string>fbshareextension</string> </array>

Sepatah Kata tentang Arsitektur BLoC

Standar arsitektur ini dijelaskan di salah satu artikel kami sebelumnya, yang menunjukkan penggunaan BLoC untuk berbagi kode di Flutter dan AngularDart, jadi kami tidak akan menjelaskannya secara detail di sini.

Ide dasar di balik ide utama adalah bahwa setiap layar memiliki kelas-kelas berikut: - view - yang bertanggung jawab untuk menampilkan status saat ini dan mendelegasikan input pengguna sebagai peristiwa untuk diblokir. - status - yang mewakili data "langsung" yang berinteraksi dengan pengguna menggunakan tampilan saat ini. - blok - yang merespons peristiwa dan memperbarui status yang sesuai, secara opsional meminta data dari satu atau banyak repositori lokal atau jarak jauh. - peristiwa - yang merupakan hasil tindakan pasti yang mungkin atau mungkin tidak mengubah keadaan saat ini.

Sebagai representasi grafis, dapat dianggap seperti ini:

Tutorial Flutter: Representasi grafis dari arsitektur BLoC.

Selain itu, kami memiliki direktori model yang berisi kelas data dan repositori yang menghasilkan instance dari kelas-kelas ini.

Pengembangan UI

Membuat UI menggunakan Flutter dilakukan sepenuhnya di Dart, berbeda dengan pengembangan aplikasi asli di Android dan iOS di mana UI dibangun menggunakan skema XML dan sepenuhnya terpisah dari basis kode logika bisnis. Kita akan menggunakan komposisi elemen UI yang relatif sederhana dengan komponen yang berbeda berdasarkan status saat ini (misalnya isLoading, parameter isEmpty). UI di Flutter berkisar pada widget, atau lebih tepatnya pohon widget. Widget dapat berupa stateless atau stateful. Ketika berbicara tentang stateful, penting untuk ditekankan bahwa, ketika setState() dipanggil pada widget tertentu yang sedang ditampilkan (memanggilnya di konstruktor atau setelah dibuang menghasilkan kesalahan runtime), build dan draw pass adalah dijadwalkan untuk dilakukan pada siklus menggambar berikutnya.

Untuk singkatnya, kami hanya akan menampilkan salah satu kelas UI (tampilan) di sini:

 class LoginScreen extends StatefulWidget { LoginScreen({Key key}) : super(key: key); @override State<StatefulWidget> createState() => _LoginState(); } class _LoginState extends State<LoginScreen> { final _bloc = LoginBloc(); @override Widget build(BuildContext context) { return BlocProvider<LoginBloc>( bloc: _bloc, child: LoginWidget(widget: widget, widgetState: this) ); } @override void dispose() { _bloc.dispose(); super.dispose(); } } class LoginWidget extends StatelessWidget { const LoginWidget({Key key, @required this.widget, @required this.widgetState}) : super(key: key); final LoginScreen widget; final _LoginState widgetState; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Login"), ), body: BlocBuilder( bloc: BlocProvider.of<LoginBloc>(context), builder: (context, LoginState state) { if (state.loading) { return Center( child: CircularProgressIndicator(strokeWidth: 4.0) ); } else { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ ButtonTheme( minWidth: 256.0, height: 32.0, child: RaisedButton( onPressed: () => BlocProvider.of<LoginBloc>(context).onLoginGoogle(this), child: Text( "Login with Google", style: TextStyle(color: Colors.white), ), color: Colors.redAccent, ), ), ButtonTheme( minWidth: 256.0, height: 32.0, child: RaisedButton( onPressed: () => BlocProvider.of<LoginBloc>(context).onLoginFacebook(this), child: Text( "Login with Facebook", style: TextStyle(color: Colors.white), ), color: Colors.blueAccent, ), ), ], ), ); } }), ); } void navigateToMain() { NavigationHelper.navigateToMain(widgetState.context); } }

Kelas UI lainnya mengikuti pola yang sama tetapi mungkin memiliki tindakan yang berbeda dan mungkin menampilkan pohon widget status kosong selain status pemuatan.

Autentikasi

Seperti yang mungkin sudah Anda duga, kami akan menggunakan pustaka google_sign_in dan flutter_facebook_login untuk mengautentikasi pengguna dengan mengandalkan profil jejaring sosial mereka. Pertama-tama, pastikan untuk mengimpor paket-paket ini ke dalam file yang akan menangani logika permintaan login:

 import 'package:flutter_facebook_login/flutter_facebook_login.dart'; import 'package:google_sign_in/google_sign_in.dart';

Sekarang, kita akan memiliki dua bagian independen yang akan menangani aliran otentikasi kita. Yang pertama akan memulai permintaan masuk Facebook atau Google:

 void onLoginGoogle(LoginWidget view) async { dispatch(LoginEventInProgress()); final googleSignInRepo = GoogleSignIn(signInOption: SignInOption.standard, scopes: ["profile", "email"]); final account = await googleSignInRepo.signIn(); if (account != null) { LoginRepo.getInstance().signInWithGoogle(account); } else { dispatch(LogoutEvent()); } } void onLoginFacebook(LoginWidget view) async { dispatch(LoginEventInProgress()); final facebookSignInRepo = FacebookLogin(); final signInResult = await facebookSignInRepo.logInWithReadPermissions(["email"]); if (signInResult.status == FacebookLoginStatus.loggedIn) { LoginRepo.getInstance().signInWithFacebook(signInResult); } else if (signInResult.status == FacebookLoginStatus.cancelledByUser) { dispatch(LogoutEvent()); } else { dispatch(LoginErrorEvent(signInResult.errorMessage)); } }

Yang kedua akan dipanggil ketika kami mendapatkan data profil dari salah satu penyedia. Kami akan menyelesaikan ini dengan menginstruksikan handler login kami untuk mendengarkan aliran firebase_auth onAuthStateChange :

 void _setupAuthStateListener(LoginWidget view) { if (_authStateListener == null) { _authStateListener = FirebaseAuth.instance.onAuthStateChanged.listen((user) { if (user != null) { final loginProvider = user.providerId; UserRepo.getInstance().setCurrentUser(User.fromFirebaseUser(user)); if (loginProvider == "google") { // TODO analytics call for google login provider } else { // TODO analytics call for facebook login provider } view.navigateToMain(); } else { dispatch(LogoutEvent()); } }, onError: (error) { dispatch(LoginErrorEvent(error)); }); } }

UserRepo dan LoginRepo tidak akan diposting di sini, tetapi silakan lihat repo GitHub untuk referensi lengkap.

Tutorial Flutter: Cara Membuat Aplikasi Pesan Instan

Akhirnya, kita sampai pada bagian yang menarik. Sesuai dengan namanya, pesan harus dipertukarkan secepat mungkin, idealnya ini harus instan . Untungnya, cloud_firestore memungkinkan kita untuk berinteraksi dengan instance Firestore dan kita dapat menggunakan fitur snapshots() untuk membuka aliran data yang akan memberi kita pembaruan secara real time. Menurut pendapat saya, semua kode chat_repo cukup mudah dengan pengecualian metode startChatroomForUsers . Ini bertanggung jawab untuk membuat ruang obrolan baru untuk dua pengguna kecuali ada yang sudah ada yang berisi kedua pengguna (karena kami tidak ingin memiliki beberapa contoh dari pasangan pengguna yang sama) dalam hal ini mengembalikan ruang obrolan yang ada.

Namun, karena desain Firestore, saat ini tidak mendukung kueri array-contains bersarang. Jadi kami tidak dapat mengambil aliran data yang sesuai tetapi perlu melakukan penyaringan tambahan di pihak kami. Solusi itu terdiri dari mengambil semua ruang obrolan untuk pengguna yang masuk dan kemudian mencari yang juga berisi pengguna yang dipilih:

 Future<SelectedChatroom> startChatroomForUsers(List<User> users) async { DocumentReference userRef = _firestore .collection(FirestorePaths.USERS_COLLECTION) .document(users[1].uid); QuerySnapshot queryResults = await _firestore .collection(FirestorePaths.CHATROOMS_COLLECTION) .where("participants", arrayContains: userRef) .getDocuments(); DocumentReference otherUserRef = _firestore .collection(FirestorePaths.USERS_COLLECTION) .document(users[0].uid); DocumentSnapshot roomSnapshot = queryResults.documents.firstWhere((room) { return room.data["participants"].contains(otherUserRef); }, orElse: () => null); if (roomSnapshot != null) { return SelectedChatroom(roomSnapshot.documentID, users[0].displayName); } else { Map<String, dynamic> chatroomMap = Map<String, dynamic>(); chatroomMap["messages"] = List<String>(0); List<DocumentReference> participants = List<DocumentReference>(2); participants[0] = otherUserRef; participants[1] = userRef; chatroomMap["participants"] = participants; DocumentReference reference = await _firestore .collection(FirestorePaths.CHATROOMS_COLLECTION) .add(chatroomMap); DocumentSnapshot chatroomSnapshot = await reference.get(); return SelectedChatroom(chatroomSnapshot.documentID, users[0].displayName); } }

Selain itu, karena kendala desain yang serupa, Firebase saat ini tidak mendukung pembaruan larik (menyisipkan elemen baru dalam nilai bidang larik yang ada) dengan nilai FieldValue.serverTimestamp() khusus.

Nilai ini menunjukkan ke platform bahwa bidang yang berisi ini alih-alih nilai aktual harus diisi dengan stempel waktu aktual di server saat transaksi berlangsung. Sebagai gantinya, kami menggunakan DateTime.now() pada saat kami membuat objek serial pesan baru kami dan memasukkan objek itu ke dalam kumpulan pesan ruang obrolan.

 Future<bool> sendMessageToChatroom(String chatroomId, User user, String message) async { try { DocumentReference authorRef = _firestore.collection(FirestorePaths.USERS_COLLECTION).document(user.uid); DocumentReference chatroomRef = _firestore.collection(FirestorePaths.CHATROOMS_COLLECTION).document(chatroomId); Map<String, dynamic> serializedMessage = { "author" : authorRef, "timestamp" : DateTime.now(), "value" : message }; chatroomRef.updateData({ "messages" : FieldValue.arrayUnion([serializedMessage]) }); return true; } catch (e) { print(e.toString()); return false; } }

Membungkus

Jelas, aplikasi perpesanan Flutter yang kami kembangkan lebih merupakan bukti konsep daripada aplikasi perpesanan instan yang siap pasar. Sebagai ide untuk pengembangan lebih lanjut, seseorang dapat mempertimbangkan untuk memperkenalkan enkripsi ujung ke ujung atau konten yang kaya (obrolan grup, lampiran media, penguraian URL). Tetapi sebelum semua itu, seseorang harus menerapkan pemberitahuan push karena itu adalah fitur yang harus dimiliki untuk aplikasi perpesanan instan, dan kami telah memindahkannya dari cakupan artikel ini demi singkatnya. Selain itu, Firestore masih kehilangan beberapa fitur untuk memiliki kueri array-contains bersarang seperti data yang lebih sederhana dan akurat.

Seperti yang disebutkan di awal artikel, Flutter baru saja matang menjadi rilis 1.0 stabil dan akan terus berkembang, tidak hanya dalam hal fitur dan kemampuan kerangka kerja tetapi juga ketika datang ke komunitas pengembangan dan perpustakaan pihak ketiga dan sumber daya. Masuk akal untuk menginvestasikan waktu Anda untuk mengenal pengembangan aplikasi Flutter sekarang, karena jelas ada di sini untuk tinggal dan mempercepat proses pengembangan seluler Anda.

Tanpa biaya tambahan, pengembang Flutter juga akan siap menargetkan OS–Fuchsia Google yang sedang berkembang.

Terkait: Bahasa Dart: Ketika Java dan C# Tidak Cukup Tajam