Flutter Eğitimi: İlk Flutter Uygulamanızı Nasıl Oluşturursunuz

Yayınlanan: 2022-03-11

Flutter nedir?

Flutter, Google'ın, ürününüzün iki ayrı kod tabanına ihtiyaç duymadan hem Android hem de iOS platformlarını aynı anda hedeflemesine olanak tanıyan mobil uygulama geliştirme SDK'sıdır. Ayrıca, Flutter kullanan uygulamalar da Google'ın yakında çıkacak olan Fuchsia işletim sistemini hedefleyecek şekilde derlenebilir.

Flutter kısa süre önce önemli bir dönüm noktasına ulaştı - kararlı sürüm 1.0. Yayın, 5 Aralık 2018'de Londra'da Flutter Live etkinliğinde gerçekleşti. Hala erken ve gelişen bir yazılım girişimi olarak kabul edilebilir olsa da, bu makale zaten kanıtlanmış bir konsepte odaklanacak ve Flutter 1.2 ve Firebase kullanan hem büyük mobil platformları hedefleyen tam işlevli bir mesajlaşma uygulamasının nasıl geliştirileceğini gösterecek.

Aşağıdaki grafikten de anlaşılacağı üzere Flutter, son aylarda oldukça fazla kullanıcı kazanıyor. 2018'de Flutter'ın pazar payı ikiye katlandı ve arama sorguları açısından React Native'i geçme yolunda ilerliyor, bu nedenle yeni bir Flutter öğreticisi oluşturma kararımız.

Temmuz ile Eylül 2018 arasında Flutter ve React kullanıcılarını karşılaştıran grafik.

Not: Bu makale, uygulamanın yalnızca belirli bölümlerine odaklanmaktadır. Proje için tam kaynak kodu referansı bu GitHub deposunda bulunabilir.

Önkoşullar

Okuyucuların mobil geliştirme konusundaki ilk girişimleri olsa bile bu projeyi takip etmeleri ve gerçekleştirmeleri için çaba sarf edilmiş olsa da, Flutter'a özgü olmayan birçok temel mobil geliştirme kavramından bahsedilmiş ve ayrıntılı bir açıklama yapılmadan kullanılmıştır.

Bu, okuyucunun projeyi bir oturuşta tamamlaması için amaçlarından biri olduğundan, makalenin kısa olması için yapılmıştır. Son olarak, makale, gerekli Android Studio eklentileri ve Flutter SDK dahil olmak üzere geliştirme ortamınızın zaten kurulu olduğunu varsayar.

Firebase Kurulumu

Her platform için bağımsız olarak yapmamız gereken tek şey Firebase'i kurmak. Öncelikle Firebase Dashboard'da yeni bir proje oluşturduğunuzdan emin olun ve yeni oluşturulan çalışma alanına Android ve iOS uygulamaları ekleyin. Platform, indirmeniz gereken iki yapılandırma dosyası üretecektir: Android için google-services.json ve iOS için GoogleService-Info.plist . Kontrol panelini kapatmadan önce, kullanıcı kimliği için kullanacağımız için Firebase ve Google kimlik doğrulama sağlayıcılarını etkinleştirdiğinizden emin olun. Bunu yapmak için menüden Kimlik Doğrulama öğesini seçin ve ardından Oturum Açma yöntemi sekmesini seçin.

Kurulumun geri kalanı kod tabanımızda gerçekleştiği için artık gösterge tablosunu kapatabilirsiniz. Öncelikle indirdiğimiz dosyaları projemize koymamız gerekiyor. google-services.json dosyası $(FLUTTER_PROJECT_ROOT)/android/app klasörüne, GoogleService-Info.plist ise $(FLUTTER_PROJECT_ROOT)/ios/Runner dizinine yerleştirilmelidir. Ardından, projede kullanacağımız Firebase kitaplıklarını gerçekten kurmamız ve bunları yapılandırma dosyalarıyla bağlamamız gerekiyor. Bu, projemizin pubspec.yaml dosyasında kullanacağımız Dart paketlerini (kütüphaneleri) belirterek yapılır. Dosyanın bağımlılıklar bölümüne aşağıdaki parçacığı yapıştırın:

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

İlk ikisi Firebase ile ilgili değil ancak projede sıkça kullanılacak. Son ikisi, umarım, açıklayıcıdır.

Son olarak, kimlik doğrulama akışımızın başarıyla tamamlanmasını sağlayacak platforma özel proje ayarlarını yapılandırmamız gerekiyor. Android tarafında, proje düzeyindeki Gradle yapılandırmamıza google-services Gradle eklentisini eklememiz gerekiyor. Başka bir deyişle, $(FLUTTER_PROJECT_ROOT)/android/build.gradle dosyasındaki bağımlılık listesine aşağıdaki öğeyi eklememiz gerekiyor:

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

Ardından $(FLUTTER_PROJECT_ROOT)/android/app/build.gradle sonuna bu satırı ekleyerek bu eklentiyi uygulamamız gerekiyor:

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

Bu platform için son şey, Facebook uygulama parametrelerinizi kaydetmektir. Burada aradığımız şey şu iki dosyayı düzenlemektir - $(FLUTTER_PROJECT_ROOT)/android/app/src/main/AndroidManifest.xml ve $(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>

Şimdi iOS zamanı. Neyse ki, bu durumda sadece bir dosyayı değiştirmemiz gerekiyor. Aşağıdaki değerleri ( CFBundleURLTypes öğesinin listede zaten mevcut olabileceğini unutmayın; bu durumda, bu öğeleri yeniden bildirmek yerine mevcut diziye eklemeniz gerekir) $(FLUTTER_PROJECT)ROOT/ios/Runner/Info.plist dosya:

 <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>

BLoC Mimarisi Üzerine Bir Söz

Bu mimari standardı, Flutter ve AngularDart'ta kod paylaşımı için BLoC kullanımını gösteren önceki makalelerimizden birinde açıklanmıştır, bu nedenle burada ayrıntılı olarak açıklamayacağız.

Ana fikrin arkasındaki temel fikir, her ekranın aşağıdaki sınıflara sahip olmasıdır: - görünüm - mevcut durumu görüntülemekten ve kullanıcı girdisini bloklanacak olaylar olarak devretmekten sorumludur. - durum - kullanıcının mevcut görünümü kullanarak etkileşimde bulunduğu "canlı" verileri temsil eder. - blok - olaylara yanıt veren ve durumu buna göre güncelleyen, isteğe bağlı olarak bir veya daha fazla yerel veya uzak depodan veri talep eden. - olay - mevcut durumu değiştirebilecek veya değiştirmeyebilecek kesin bir eylem sonucu.

Grafik gösterimi olarak şöyle düşünülebilir:

Flutter Eğitimi: BLoC mimarisinin grafik gösterimi.

Ek olarak, veri sınıflarını ve bu sınıfların örneklerini üreten havuzları içeren bir model dizinimiz var.

Kullanıcı Arayüzü Geliştirme

Kullanıcı arabiriminin XML şeması kullanılarak oluşturulduğu ve iş mantığı kod tabanından tamamen ayrıldığı Android ve iOS'taki yerel uygulama geliştirmenin aksine, Flutter kullanarak kullanıcı arabirimi oluşturma tamamen Dart'ta yapılır. Mevcut duruma dayalı olarak farklı bileşenlerle (ör. isLoading, isEmpty parametreleri) nispeten basit UI öğesi kompozisyonları kullanacağız. Flutter'daki UI, widget'lar veya daha doğrusu widget ağacı etrafında döner. Widget'lar durumsuz veya durumlu olabilir. Durum bilgili olanlar söz konusu olduğunda, şu anda görüntülenen belirli bir pencere setState() çağrıldığında (yapıcıda çağrılması veya atıldıktan sonra çalışma zamanı hatasıyla sonuçlanır), bir oluşturma ve çizme geçişinin olduğunu vurgulamak önemlidir. sonraki çizim döngüsünde gerçekleştirilmek üzere planlanmıştır.

Kısaca, burada UI (görünüm) sınıflarından yalnızca birini göstereceğiz:

 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); } }

Kullanıcı arabirimi sınıflarının geri kalanı aynı kalıpları takip eder, ancak farklı eylemlere sahip olabilir ve yükleme durumuna ek olarak boş bir durum pencere öğesi ağacına sahip olabilir.

kimlik doğrulama

Tahmin edebileceğiniz gibi, sosyal ağ profillerine dayanarak kullanıcının kimliğini doğrulamak için google_sign_in ve flutter_facebook_login kitaplıklarını kullanacağız. Her şeyden önce, bu paketleri oturum açma isteği mantığını işleyecek dosyaya aktardığınızdan emin olun:

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

Şimdi, kimlik doğrulama akışımızla ilgilenecek iki bağımsız parçamız olacak. İlki, bir Facebook veya Google oturum açma isteği başlatacak:

 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)); } }

İkincisi, her iki sağlayıcıdan da profil verilerini aldığımızda çağrılacak. Bunu, oturum açma işleyicimize firebase_auth onAuthStateChange akışını dinlemesi talimatını vererek gerçekleştireceğiz:

 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 ve LoginRepo uygulaması burada yayınlanmayacak, ancak tam referans için GitHub deposuna bir göz atmaktan çekinmeyin.

Flutter Eğitimi: Anında Mesajlaşma Uygulaması Nasıl Oluşturulur

Sonunda ilginç kısma geliyoruz. Adından da anlaşılacağı gibi, mesajlar mümkün olduğunca hızlı değiş tokuş edilmelidir, ideal olarak bu anlık olmalıdır. Neyse ki, cloud_firestore , Firestore örneği ile etkileşime girmemize izin veriyor ve bize gerçek zamanlı olarak güncellemeler verecek bir veri akışı açmak için snapshots() özelliğini kullanabiliriz. Bence, startChatroomForUsers yöntemi dışında tüm chat_repo kodu oldukça basittir. Her iki kullanıcıyı içeren mevcut bir sohbet odası yoksa (aynı kullanıcı çiftinin birden fazla örneğine sahip olmak istemediğimizden), iki kullanıcı için yeni bir sohbet odası oluşturmaktan sorumludur, bu durumda mevcut sohbet odasını döndürür.

Ancak, Firestore'un tasarımı nedeniyle, şu anda iç içe array-contains sorguları desteklememektedir. Dolayısıyla uygun veri akışını alamıyoruz, ancak bizim tarafımızda ek filtreleme yapmamız gerekiyor. Bu çözüm, oturum açmış kullanıcı için tüm sohbet odalarını almaktan ve ardından seçilen kullanıcıyı da içeren sohbet odalarını aramaktan ibarettir:

 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); } }

Ayrıca, benzer tasarım kısıtlamaları nedeniyle, Firebase şu anda özel FieldValue.serverTimestamp() değeriyle dizi güncellemelerini (mevcut bir dizi alanı değerine yeni öğe ekleme) desteklememektedir.

Bu değer, platforma, gerçek bir değer yerine bunu içeren alanın, işlemin gerçekleştiği anda sunucudaki gerçek zaman damgasıyla doldurulması gerektiğini belirtir. Bunun yerine, yeni mesaj serileştirilmiş nesnemizi oluşturduğumuz ve bu nesneyi sohbet odası mesajları koleksiyonuna eklediğimiz anda DateTime.now() kullanıyoruz.

 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; } }

Toplama

Açıkçası, geliştirdiğimiz Flutter mesajlaşma uygulaması, piyasaya hazır bir anlık mesajlaşma uygulamasından daha çok bir kavram kanıtıdır. Daha fazla geliştirme fikri olarak, uçtan uca şifreleme veya zengin içerik (grup sohbetleri, medya ekleri, URL ayrıştırma) sunmayı düşünebiliriz. Ancak tüm bunlardan önce, bir anlık mesajlaşma uygulaması için hemen hemen olmazsa olmaz bir özellik olduğu için anında iletme bildirimleri uygulanmalıdır ve kısalık olması için onu bu makalenin kapsamından çıkardık. Ek olarak, Firestore'da daha basit ve daha doğru veri benzeri iç içe array-contains sorgulara sahip olmak için birkaç özellik eksik.

Makalenin başında belirtildiği gibi, Flutter son zamanlarda kararlı 1.0 sürümüne olgunlaştı ve yalnızca çerçeve özellikleri ve yetenekleri söz konusu olduğunda değil, aynı zamanda geliştirme topluluğu ve üçüncü taraf kitaplıklar söz konusu olduğunda da büyümeye devam edecek. Kaynaklar. Flutter uygulama geliştirme ile tanışmak için şimdi zaman ayırmanız mantıklı, çünkü kesinlikle burada kalmak ve mobil geliştirme sürecinizi hızlandırmak için burada.

Flutter geliştiricileri, hiçbir ek ücret ödemeden Google'ın gelişmekte olan OS-Fuchsia'sını hedeflemeye hazır olacak.

İlgili: Dart Dili: Java ve C# Yeterince Keskin Olmadığında