Firebase ve Flutter ile hızlı ve iyi proje geliştir [öğren, paylaş, bağış yap]

Veli Bacık
12 min readApr 1, 2023

Selamlar bugün sizlere öğrenirken değer katabileceğiniz bir içerik serisini tek tek anlatıp düşüncelerimi paylaşıyorum. Eğitim ile bu yaraları sarıp kalan halkımız için çalışmaya çabalamaya devam edeceğiz.

Başlarken:

Deprem olduğu günden beri gerek fiziksel gerek mental o bölgelere elimden gelen desteği verdim ama biliyorum ki bizler çabuk unutuyoruz. Üzerinden neredeyse iki ay geçtiği şu günlerde bir çoğumuz belki yavaştan unuttu o insanları. Bu seri iki noktayı hedefliyor:

  • Deprem bölgelerine yardımı bir sorumluluk haline getirmek
  • Bir fikri nasıl tüm platformlarda neredeyse hiçbir maliyet olmadan yayına almayı düşünce ve iyi bir yaklaşımla almayı hedefliyorum.

Nasıl katkı sağlarım?

  • Projenin tüm yapısı kodları youtube serisinde yer alıyor. Alt kısımda bunları biraz özetleyeceğim. Burada tek isteğim kodları almak isteyen veya destek olmak isteyenler herhangi bir kuruma bağış yapıp bu dekontları ilgili form alanına yüklemeleri yeterli olacaktır.

Eski tarihli bağışları kabul etmiyorum. Burada amacımız bu seri ile hem bir fikir hem gelir üretmek.

Bağış bildiri formu: https://forms.gle/4yb45rRZDWR99bBQA

Haydi gelin projeyi içerikleri tek tek inceleyip çıktılarına bakalım.

İçerik yaklaşık 8 saat sürdü. Baştan sona tüm kodlar burada yapıldı. Teknik kısımı özetliyecek olursam:

  • iOS, Android ve Web ön yüz için Flutter tercih ettim.
  • Verilerimi saklayıp her yerden erişmek için Firebase kullandım.

Bu içerik doğrudan bir fikri hayata geçirmeyi hedeflediği için hiç bilmeyenler için uygun değil o kısımda yine benim kanalımda bulunan veya sektörden yukarıdaki teknik kavramların giriş videolarına biraz göz atsanız yeterli.

Benim neredeyse tüm içeriklerim sıfırdan başlayarak gider ondan ötürü izleyip aynısını yapsanız çok önemli olacaktır. Genel de giriş içerikleri değil de daha ileri ve düşünce katmayı hedeflediğim için biraz zor gelebilir aynı gerçek hayatta bir şirkette çalışmak gibi.

1- Neden Flutter? Neden Firebase? Analiz Yapma, Proje Detayları

Bu tarz bir işin ana noktası hızlı geliştirmektir. Bu noktada çok fazla geliştirici de elinizde olmayabiliyor. Sizden bir çok alana bakmanız istendiğinde arayacağınız en önemli nokta “tek koda çok çıktı” oluyor.

İşte bu noktada Flutter özellikle son versiyonları ile web konusunda oldukça gelişti. Doğrudan sonuca gitmeyi düşündüğümüz işlerde çok fazla işe yarar hale geldi. Çünkü günün sonunda dart dili ile birlikte ios android web için bize Material design doğrudan içinde olan bir çıktı üretebiliyor.

Bunu seçmeseydik bu sefer her ortamı native yazma ve çıktılarıyla tek tek uğraşmak gerekecek. Bunun test ve bakım maliyeti özellikle çok yüksek oluyor. Ondan ötürü daha ilk adımlarını atan startup’lar projeler için flutter çok iyi bir tercih oluyor.

Bir de tabiki hızlı bir proje için önemli olan sadece kod değil bir nebze kodun da iyiliğidir. Bunun için doğru analiz ve doğru ekipmanlar kullanılıyor olmalı. Özellikle tasarım için figma vb. toolar ile geliştiriciye tam bir çalışma ortamı sunulur.

Benim en çok önemsediğim nokta analiz oluyor. Çünkü bir çok projede analiz ve ne istendiği tam bilinmediği için geliştirici bir çok hata yapmaya hele ki tecrübesiz bir arkadaş ise çok daha fazla hataya müsait oluyor. Bunu engellemenin yolu ne istediğini bilen işler, istekler yapmak.

2: Flutter Projesi Mimari Hazırlama, Gerekli Paketleri Kurma

Geldik kodlama noktasına. Burada başlarken öncelikle bir iskelet çıkartıyoruz. Bu iskelet projenin neyin nerede olacağını belirleme, hangi paketleri kullanacağını konuşma ve nasıl bir kodlama gideceğini özetliyor.

Bu projede view, viewmodel ilişkisinde bir yapı ile ilerledim üstüne servislerim vs çok fazla olmayacağı için onları tek bir yerde toplayarak kodlama hızımı ve kontrolü oldukça artırmış oldum

Bu tarz 3–4 servisli işlerin aslında çok fazla state yönetimi, navigasyon yönetimi gibi ihtiyacı yok. Özellikle ana nokta paket hiçbir zaman için projenin ana noktası olmamalı. Yoksa akıllara benim hep söylediğim bir cümle gelir:

Paket developer olursan, Bir gün paket olursun

Burada önemli olan firebase paketlerini dahil etmek, state yönetimi olsun yinede kontrollü gitme adına riverpod kullanmak ve aslında temiz kodlamanın paketle olmadığını konuşacağız. Hızlı gitsekte birkaç hareketle temiz ya da temize gidebilecek yolları ekleyebileceğimizi de göreceğiz.

class HomeNotifier extends StateNotifier<HomeState> with FirebaseUtility {
HomeNotifier() : super(const HomeState());
Future<void> fetchNews() async {
final newsCollectionReference = FirebaseCollections.news.reference;
final response = await newsCollectionReference.withConverter(
fromFirestore: (snapshot, options) {
return const News().fromFirebase(snapshot);
},
toFirestore: (value, options) {
return value.toJson();
},
).get();

if (response.docs.isNotEmpty) {
final values = response.docs.map((e) => e.data()).toList();
state = state.copyWith(news: values);
}
}
}

Bu içerikte temel olarak klasörleme vs yapıp üzerine riverpod örneği yapıp basitçe pekiştirmiş oluyoruz.

Burada ki içeriğin sonunda bu klasörlerin hepsini birlikte yapmış olacağız. Burada bir önemli paket very_good_analysis paketi ile bize gelecek linter kuralları. Muhakkak bunun detayına bakıp projenize ekleyin birkaç rule kapatmanız hız için iyi oluyor onları kodlarda görebileceksiniz.

3: Firebase Entegrasyonu ve Alt Yapı Hazırlama

Ve geldik en önemli kısıma. Bu işlem ile birlikte projemizi internette her yerde kullanabilecek bir sunucuya açmış olacağız. Burada amaç hem database olarak hem de bunun önüne paketin getirmiş olduğu güç ile firebase eklemek.

Flutter ve Firebase birlikte kullanmak tek bir kod ile aslında hem backend hem mobil yazmak oluyor. Ama client yani telefon uygulamalarında backend olması problemli bir nokta içerikte bunlardan bahsediyorum

Burada ihtiyaç olan paketler hangi şekilde kullanacağımızı ele aldım.

 #Firebase
cloud_firestore: ^4.4.3
firebase_core: ^2.7.1
firebase_auth: ^4.2.9
firebase_auth_web: ^5.2.9
firebase_ui_auth: ^1.1.15
firebase_ui_oauth_google: ^1.0.22
firebase_storage: ^11.0.15

Bu paketleri kullanmak için yapmanız gereken basit kurulumu da yine içerikte görebilirsiniz. Firebase cli ile çok hızlı bir şekilde ekliyoruz. Tüm işlemlerden sonra artık hazır oluyoruz bağlamaya.

Cloud Firestore örneği projeden.
Giriş yöntemi projeden

Bunları ekledikten sonra basitçe birkaç katman yazımını misal FirebaseUtility katmanı hazırlıyoruz. Bu kod tam benim aklımda olanın aslında özeti oluyor. Evet hızlı gideceğiz ama önüne basit bir katman yazarak bunu daha nasıl generic ve çoklu yönetirizi öğrenmiş oluyoruz.

Bu kodun tüm teknik detayı yine video içerisinde sizlerle mevcut. Burada amaç koda odaklanmak fikre vermek kendimizi. Son olarak birkaç daha hareket misal bir enum ekleyerek collection’ları yönetmeyi daha kolay esnek yapıyoruz.

import 'package:cloud_firestore/cloud_firestore.dart';

enum FirebaseCollections {
news,
tag,
recommended,
version,
category;

CollectionReference get reference =>
FirebaseFirestore.instance.collection(name);
}

4: Giriş(Splash) Ekranı, Platform Özel Kod Yazma ve Zorunlu Güncelleme

Her projede olmazsa olmaz ama firebase projelerinde kesinlikle olması gereken bir noktaya geldik. Bu projemizin ilk açılış ekranı olarak geçen kısım bu kısımda biz projenin gerekli detaylarını kontrol ediyoruz. Misal

  • Versiyon kontrolü
  • Token kontorlü
  • Bazı temel yapıların database vs yüklenmesi
  • Kullanıcıya bilgi verme (kampanya vs)

Burada firebase kullandığımız için bir nokta vermek isterim: (videolarda sık sık bahsettiğim bir konu)

Biz firebase ile database bağlantımızı sağlıyoruz. Bu işlemi telefonda yani kod yazdığımız client içinde yazıyoruz. Bunun en büyük sıkıntısı herhangi bir hata çıktığında telefon da çalışan kodu doğrudan değiştiremezsiniz. Bir web sitesini gerekirse kaldırır güncelleyebilirsiniz ama bir mobil uygulama için geçerli değil bu durum. Bir güncelleme için bazen bir hafta beklemeniz bile gerekiyor.

Burada misal bir versiyon kontrolü için database de bir ufak bir dokuman oluşturuyorum. Ardından alttaki gibi basit anlaşılır bir kodla bunu çekip ekrana haber veriyorum

 Future<void> checkApplicationVersion(String clientVersion) async {
final databaseValue = await getVersionNumberFromDatabase();

if (databaseValue == null || databaseValue.isEmpty) {
state = state.copyWith(isRedirectHome: false);
return;
}

final checkIsNeedForceUpdate = VersionManager(
deviceValue: clientVersion,
databaseValue: databaseValue,
);

if (checkIsNeedForceUpdate.isNeedUpdate()) {
state = state.copyWith(isRequiredForceUpdate: true);
return;
}

state = state.copyWith(isRedirectHome: true);
}

Riverpod’u bu kısımda entegre ettim ve basit bir ekran dahi olsa kullanımını hem geliştirmek hem de bir ufak state yönetimi anlamak için yapmış olduk. Bu kısımda en çok dikkat edilmesi gereken alt kısımda göreceğiniz kod bloku. Bunu özellikle video da anlatıyorum Equtable ile kullanımı ve state koruyarak haberleşmeyi göreceksiniz

-5: Firebase Auth Entegrasyonu ve Ekran Tasarımı (flutterfire_ui)

Serinin en güzel kısımlarından birisi aslında. Burada flutter firebase ekibinin bize verdiği gücü kullanarak çok hızlıca giriş ekranı geliştiriyoruz.
Bu paket bize
- sosyal medya giriş (google play, apple vs)
- özel giriş (herhangi bir email vs)

Bunları flutter ui paketi ile her şeyi hızlıca entegre edebiliyoruz. Proje başlarken sadece hangi auth yöntemlerini kullanacağımı söylememiz yeterli oluyor

   FirebaseUIAuth.configureProviders(
[EmailAuthProvider(), GoogleProvider(clientId: '')],
);

Bizim için otomatik ekranda google login buton’u ve mail password olan bir yapıyı validasyon dahil hazır sağlıyor. Alt kısımda görüldüğü üzere hepsi bize hazır geliyor ve içerikte burayı nasıl özelleştireceğimizi de görüyoruz.

Ek olarak eğer herhangi biri giriş yapar ve bunu dinlemek isterseniz şu tarz bir kod ile bulabiliyorsunuz.

 firebase.FirebaseUIActions(
actions: [
AuthStateChangeAction<SignedIn>((context, state) {
if (state.user != null) checkUser(state.user);
}),
],
..

6: Ana Sayfa Ekran Tasarımı, Küçük Parçalara Ayırma

En önemli noktalardan birisi aslında ekranlarımızı yaparken bol bol kodlarımızı parçalamayı yapmamız gerekiyor biliyoruz fakat genelde böyle projelerde hız göz önüne alınıp unutuluyor.

Özellikle flutter projelerinde doğru parçalama projenin her noktası oluyor misal üstteki ekranı yapmak için bu içerikte tek tek nasıl parçalayacağınızı anlattım gösterdim

 ListView(
padding: context.paddingNormal,
children: [
const _Header(),
_CustomField(_controller),
const _TagListView(),
const _BrowseHorizontalListView(),
const _RecommendedHeader(),
const _RecommendedListView(),
],
),

Bu kullanım çok önemli. Burada amaç bu düşünceyi edinmek olması aslında. Çünkü kodlarken o hızlı yazıyorum ekran çıkıyor diye gidildikçe çok zor component’ler oluşuyor. Bazen binlerce satır iç içe girmiş ekran kodlarında kaybolup gidiyorum sırf bu eksiklik yüzünden. Bunda dikkat etmek çok çok önemli.

Bu tarz hız esaslı projede en çok gözden kaçanlar elinizde olan textlerin direk ekranda tek tek yazılması ve color, padding gibi bir çok property hard code yani doğrudan misal Color(0xff) gibi yazıp bırakmaktır. Bununla alakalı seride nasıl daha iyi yapacağınızı ve basitçe şu kodları yapıp o projeyi daha sağlıklı hale getireceğinizi göreceksiniz.

// Text
const TitleText(
value: StringConstants.homeBrowse,
),
// Theme
return Text(
value,
style: context.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
);
// Color
InputDecoration(
suffixIcon: Icon(Icons.mic_outlined),
prefixIcon: Icon(Icons.search_off_outlined),
border: OutlineInputBorder(
borderSide: BorderSide.none,
),
filled: true,
fillColor: ColorConstants.grayLighter,
hintText: StringConstants.homeSearchHint,
)

// Padding kartal package
Padding(
padding: context.onlyTopPaddingLow)

Bunların detayları içerikte yer alıyor. Burada önemli olan bu düşüncede olmak. Bu proje yarınlar yok gibi değil de bunları tek bir yerde toplayıp projenin detaylarını görmemiz gerekiyor.

Misal textlerinizi bu şekilde toplu bir sınıfta tutabilirsiniz. İllaha bir paket bir yapı koymadan bunları yaparak o hızlı geliştirme yaparken gücü elden bırakmamış olursunuz.

Tema için de flutter hiçbir şey oluşturmadan size belirli bir tema ve renkler veriyor. Bunlar bu tarz projelerde oldukça yeterli. Bunlara bakarak projenin bir tema içinde gitmesini sağlayabilirsiniz.

https://api.flutter.dev/flutter/material/TextTheme-class.html

-7: Ana Sayfa Firebase Entegrasyonu Listeleme ve Arama

Projenin ana sayfasına geldik. Bu sayada artık ekranımızın tasarımı hazır database bağlantısını yapacağız. Burada firebase kısmını ele alıyorum tek tek içerikte ama collection yani tablonuz diyebilirsiniz, bunu oluştururken bir ufak id üzerinden bağlama yapmanız gerekiyor

Burada bir haberimiz var. Bu haberin kategorisini verirken isminin yanı sıra id veriyoruz. Bu sayede kategori de misal bir güncelleme oldu veya haberlerin için de bu kategori var mı demek için o id değerini kullanıyoruz.

Basitçe aslında iki veriyi birbirine bağlamış oluyoruz. Buradan sonra artık işimiz bilgileri çekmeye geldi. Bunun için riverpod özelinde bir view model dosyası(home_provider) yaparak ekranla haberleşmeyi sağlıyoruz. İkinci etapta datalarımızı çekerek ekrana bağlıyoruz.

  Future<void> fetchNews() async {
final newsCollectionReference = FirebaseCollections.news.reference;
final response = await newsCollectionReference.withConverter(
fromFirestore: (snapshot, options) {
return const News().fromFirebase(snapshot);
},
toFirestore: (value, options) {
return value.toJson();
},
).get();

if (response.docs.isNotEmpty) {
final values = response.docs.map((e) => e.data()).toList();
state = state.copyWith(news: values);
}
}

Burada firebase den verilerimizi çekip ekrana doğrudan göstermeyi sağlıyoruz riverpod kullanarak. Buranın teknik detayı videoda bol bol bulacaksınız ama burada önemli olan nokta misal FromFirebase methodu ne işe yarıyor, neden önüne böyle bir katman ekledik. Muhakkak bu konuya odaklanın.

Servisimizden başarılı sonuç geldikten sonra örnek bir widget ta bu şekilde kodumuzu bağlıyoruz:

class _TagListView extends ConsumerWidget {
const _TagListView();
@override
Widget build(BuildContext context, WidgetRef ref) {
final newsItems = ref.watch(_homeProvider).tags ?? [];
return SizedBox(
height: context.dynamicHeight(.1),
child: ListView.builder(
itemCount: newsItems.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
final tagItem = newsItems[index];
if (tagItem.active ?? false) {
return _ActiveChip(tagItem);
}
return _PassiveChip(tagItem);
},
),
);
}
}

Bazı teknik kavramları riverpod vs belki bilmiyor olabilirsiniz ama burada önemli olan bu parçalama ile işlerinizi bölme olmalı. Setstate yani pure kullansan dahi parçalaman gerektiği noktasıdır.

Son olarak bu sayfada dikkat edilmesi gereken search işlemi. Firebase doğrudan desteklemiyor içerikte bahsediyorum üzerine bende dataları komple çekip buradan gösteriyorum. Siz burada şunu yapabilirsiniz dataları komple çekin, herhangi bir yerde saklayın ve tüm dataları tek tek çekmek yerine son dataları karşılaştırın her açıldığında yoksa cacheden kullan diyerek maaliyeti (okuma/yazma) düşürmüş olun.

  final response = await showSearch<Tag?>(
context: context,
delegate: HomeSearchDelegate(
ref.read(_homeProvider.notifier).fullTagList,
),
);

Search için flutter’in bize vermiş olduğu hazır ekranı kullanıp çok hızlıca bağlayıp güzel bir sayfa elde etmiş oluyoruz.

-8: Firebase Yeni Veri ekleme, Validasyonlarını Sağlama

Serinin en uzun kısmı. Burada tek tek her şeyi parça parça ele alıyorum aslında. Bir amacım şu oluyor aslında bir paket olmadan da bu ekranları nasıl temiz yazabileceğinizi gösterme diğeri ise firebase ile olan bağlantı.

Veri eklerken:
- Resim ekleyip, başlık ile oluşturuyoruz.
(Bu kısımda firebase database ve firebase stroage kullanımı görüyoruz)
- Form ekranın da validasyonları parçalama ve nasıl yönetim sağlayacağımızı görüyoruz
- Dropdown kullanımı
ve birkaç ui hareketini görmüş oluyoruz.

Misal benim sevdiğim kullanımlardan her sayfada kullanılacak isLoading ve change loading’i şu şekilde bir mixin ile generic hale getiriyorum

mixin Loading on State<HomeCreateView> {
bool isLoading = false;
void changeLoading() {
setState(() {
isLoading = !isLoading;
});
}
}

// Home
class _HomeCreateViewState extends State<HomeCreateView> with Loading {
...
if (isLoading)
const Center(
child: CircularProgressIndicator(
color: ColorConstants.white,
),
)

Benim en çok sevdiğim kodlardan form ekranında misal validasyon olana kadar buton kapalı validasyon olunca açık olmasını istiyorsunuz. Bunu on change ile yapabiliyorsunuz ama ekranı her defa tetiklemek yerine önüne şöyle bir kod ekleyerek performans sağlamış oluyorsunuz.

Ve ekran şu şekilde çağırarak kullanıyor

  body: Form(
key: _homeLogic.formKey,
onChanged: () {
_homeLogic.checkValidateAndSave(
(value) {
setState(() {});
},
);
},

Burada dikkatinizi çekecek nokta home logic instance değeri. Bu içerikte yine çok önemli noktalaradan birisi aslında.

Veri ekleme sayfasında bilerek riverpod eklemedim dedim ki hiçbir şey olmasaydı nasıl olurdu. HomeLogic bunun için var aslında. Bu sayede aslında o parçaladığımız widgetler olsun core component yazarken olsun nasıl bir yapı kuracağımızı görmüş oluyoruz.

class HomeLogic with FirebaseUtility {
final TextEditingController titleController = TextEditingController();
CategoryModel? _categoryModel;
List<CategoryModel> _categories = [];
Uint8List? _selectedFileBytes;
XFile? _selectedFile;

bool isValidateAllForm = false;
final GlobalKey<FormState> formKey = GlobalKey();
List<CategoryModel> get categories => _categories;
Uint8List? get selectedFileBytes => _selectedFileBytes;

void updateCategory(CategoryModel categoryModel) {
_categoryModel = categoryModel;
}

bool checkValidateAndSave(ValueSetter<bool>? onUpdate) {}
...

Bu kullanımı katabilirsek kendimize çok güzel bir özellik daha aklımıza gelmiş ve yeni ekranlar yaparken düşünmüş olacağız.

Son olarak önemli kısım burada yüklediğimiz resimi ilgili modele verme noktası. Temel mantıkta önce bir tabloda veri oluşturucaz. Sonra resimi sunucuya yükleyip gelen indirme adresini bu tabloya ekleyeceğiz. Düşünce olarak şu şekilde bir yapı olacak özetle:

 final imageReference = createImageReference();
if (imageReference == null) throw ItemCreateException('image not empty');
if (_selectedFileBytes == null) return false;
await imageReference.putData(_selectedFileBytes!);
final urlPath = await imageReference.getDownloadURL();
final response = await FirebaseCollections.news.reference.add(

İçeriğin detayında hepsi tek tek anlatılıyor. Bunların yanı sıra exception oluşturma vs de sizinle olacak. Uzun bir içerik elimden geldiğince hepsini tek tek ele aldım burada.

-9: Github İş Yönetimi- Proje Sonu Değerlendirme

Ve artık sona geldik. Ara ara bahsederim iyi veya hız için çok koşmak değil doğru koşmak tam koşmak gerekiyor. Öbür türlü aslında yerimiz de sayıyoruz. Bu son kısımda projeyi değerlendirip proje yönetim kısmını ele alıyorum Github üzerinden neler yapabileceğimizi anlatıyorum.

Ve birkaç yaşadığım hatayı misal firebase core paketten gelen hatayı ve nasıl çözüm bulduğumuzu ele aldım.

Şimdiden düşünceniz paylaşımınız için çok teşekkür ederim. Ben halkını seven önce vatanım diyen ve paylaşmaktan gurur duyan bir gencim. Yaptığım veya bu içeriğin hiçbir yerinde kişisel bir sosyal çıkar, kazanç yoktur. Benimle bu yolu paylaşan fikir katan herkese teşekkürlerimi sunarım.

Bu an aklımdan hiç çıkmıyor. Neden ben neden sen değilsin diye sormadan geçemiyorum. Geçmişi düzeltemiyorum ama geleceği belki birlikte düzeltiriz. Umarım bu kadar büyük bir yıkım daha yaşanmaz. Çalışacağız işimizin en iyisini yapıp bu topluma olan borcumuzu ödeyeceğiz.

Son olarak:

“Vatanın bütün ümidi ve geleceği size, genç nesillerin anlayış ve enerjisine bağlanmıştır.” — M.K.A

Bağış bildiri formu: https://forms.gle/4yb45rRZDWR99bBQA

--

--

Veli Bacık

We always change the world, so just want it. [OLD]Google Developer Expert Flutter & Dart, Gamer, Work More!