FlutterFlutter · Chapterبەش 10 · Lectureوانە 10.3

Firebase and Cloud FirestoreFirebase و ⁦Cloud Firestore⁩

Connecting a Flutter app to Firebase with FlutterFire (flutterfire configure, DefaultFirebaseOptions.currentPlatform), Cloud Firestore's collections, documents and fields, reading live with snapshots() and StreamBuilder, create with add/set, update with update or merge, delete with delete(), choosing document ids, Security Rules, and the lecture's student CRUD app corrected and rebuilt.

بەستنەوەی Flutter بە Firebaseـەوە بە ⁦FlutterFire (flutterfire configure⁩، ⁦DefaultFirebaseOptions.currentPlatform⁩)، ، و ⁦Cloud Firestore⁩، خوێندنەوەی ڕاستەوخۆ بە ⁦snapshots()⁩ و StreamBuilder، دروستکردن بە ⁦add/set⁩، نوێکردنەوە بە update یان merge، سڕینەوە بە ⁦delete()⁩، هەڵبژاردنی ، ⁦Security Rules⁩ و قوتابییانی وانەکە کە چاک کراوە و دووبارە دروست کراوەتەوە.

20 min read20 خولەک خوێندنەوە · 16 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Set up FlutterFire with flutterfire configure and initialise Firebase in main().
  • Describe Firestore's data model: collections, documents, fields and document ids.
  • Show a collection live with snapshots() and StreamBuilder, handling errors, loading and empty data.
  • Create, update and delete documents with add, set, update, merge and delete, and handle FirebaseException.
  • Explain why Security Rules, not hidden keys, protect a Firestore database.
  • FlutterFire بە ⁦flutterfire configure⁩ ئامادە بکەیت و Firebase لە ⁦main()⁩ـدا دەست پێ بکەیت.
  • Firestore وەسف بکەیت: ، ، و .
  • بە ⁦snapshots()⁩ و StreamBuilder بە ڕاستەوخۆ پیشان بدەیت و مامەڵە لەگەڵ ، چاوەڕوانی و دراوەی بەتاڵ بکەیت.
  • بە add، set، update، merge و delete دروست بکەیت، نوێ بکەیتەوە و بسڕیتەوە و مامەڵە لەگەڵ FirebaseException بکەیت.
  • ڕوون بکەیتەوە بۆچی ⁦Security Rules⁩، نەک شاراوە، Firestore دەپارێزن.

Data that lives in the cloudئەو دراوەیەی لە هەوردا دەژی

In the last lesson the data stayed on one phone. Many apps need more: a class list that every teacher sees, a chat, a shop's stock. That data must live on a server that every phone can reach, and the screens should update by themselves when someone else changes it.

لە وانەی پێشوودا دراوەکە لەسەر یەک مۆبایل مایەوە. زۆر پێویستیان بە زیاترە: لیستی پۆلێک کە هەموو مامۆستایەک دەیبینێت، چاتێک، کاڵای کۆگای دوکانێک. ئەو دراوەیە دەبێت لەسەر بژی کە هەموو مۆبایلێک پێی بگات. شاشەکانیش دەبێت کاتێک کەسێکی تر دەیگۆڕێت خۆیان نوێ ببنەوە.

Building and running such a server yourself is a lot of work. Firebase is Google's Backend-as-a-Service: it runs the servers, and your app talks to them through a ready-made library (see Firebase: an example of BaaS). Its main database for apps is Cloud Firestore. This lesson connects a Flutter app to Firebase and builds the lecture's student app: create, read, update and delete (CRUD) students, live.

دروستکردن و بەڕێوەبردنی وا بە خۆت کارێکی زۆرە. Firebase BaaSـی Googleـە: بەڕێوە دەبات و لە ڕێگەی ئامادەوە قسەیان لەگەڵ دەکات (بڕوانە Firebase: نموونەیەکی BaaS). سەرەکیی بۆ ⁦Cloud Firestore⁩ـە. ئەم وانەیە Flutter بە Firebaseـەوە دەبەستێتەوە و قوتابییەکانی وانەکە دروست دەکات: قوتابی دروست دەکات، دەخوێنێتەوە، نوێ دەکاتەوە و دەسڕێتەوە ()، بە شێوەی ڕاستەوخۆ.

Quick checkپشکنینی خێراWhy would a class-list app use Firestore instead of SQLite on the phone?بۆچی لیستی پۆل Firestore بەکار دەهێنێت لە جیاتی SQLite لەسەر مۆبایلەکە؟

FlutterFire: connecting the app to FirebaseFlutterFire: بەستنەوەی ئەپەکە بە Firebaseـەوە

FlutterFire is the set of official Flutter plugins that connect a Flutter app to Firebase: firebase_core (always needed), cloud_firestore, firebase_auth, and one plugin for each other product. Before you start you need a Flutter project and a Google account to create a Firebase project in the Firebase console. Then the setup is done from the terminal:

FlutterFire کۆمەڵە فەرمییەکانی Flutterـە کە Flutter بە Firebaseـەوە دەبەستنەوە: firebase_core (هەمیشە پێویستە)، cloud_firestore، firebase_auth و یەک بۆ هەر بەرهەمێکی تر. پێش دەستپێکردن پێویستت بە پڕۆژەیەکی Flutter و هەژمارێکی Google هەیە بۆ دروستکردنی پڕۆژەیەکی Firebase لە کۆنسۆڵی Firebaseـدا. پاشان ئامادەکردنەکە لە دەکرێت:

  1. Install the Firebase CLI, then sign in⁦Firebase CLI⁩ دابمەزرێنە، پاشان بچۆ ژوورەوە

    Run firebase login and choose your Google account.

    firebase login جێبەجێ بکە و هەژماری Googleـەکەت هەڵبژێرە.

  2. Install the FlutterFire CLI⁦FlutterFire CLI⁩ دابمەزرێنە

    Run dart pub global activate flutterfire_cli once, from any folder.

    یەک جار، لە هەر ، dart pub global activate flutterfire_cli جێبەجێ بکە.

  3. Configure the projectپڕۆژەکە ڕێکبخە

    In your project folder run flutterfire configure. Pick (or create) the Firebase project and the platforms. The tool registers an Android, iOS and web app in Firebase and writes lib/firebase_options.dart.

    لەناو پڕۆژەکەتدا flutterfire configure جێبەجێ بکە. پڕۆژەی Firebaseـەکە و هەڵبژێرە (یان دروستیان بکە). ئامرازەکە Android، iOS و وێب لە Firebaseـدا تۆمار دەکات و lib/firebase_options.dart دەنووسێت.

  4. Add the pluginsپاشکۆکان زیاد بکە

    flutter pub add firebase_core cloud_firestore, then run flutterfire configure again whenever you add a platform or a Firebase product.

    flutter pub add firebase_core cloud_firestore، پاشان هەر کاتێک یان بەرهەمێکی Firebase زیاد دەکەیت دووبارە flutterfire configure جێبەجێ بکە.

BASH
$ firebase login
$ dart pub global activate flutterfire_cli
$ flutterfire configure
$ flutter pub add firebase_core cloud_firestore
Quick checkپشکنینی خێراWhat does flutterfire configure produce in your project?flutterfire configure چی لە پڕۆژەکەتدا دروست دەکات؟

FlutterFire: connecting the app to FirebaseFlutterFire: بەستنەوەی ئەپەکە بە Firebaseـەوە

Starting Firebase in main()دەستپێکردنی Firebase لە main()ـدا

Firebase must be started once, before the app runs. main becomes async, makes sure Flutter's plugin system is ready, and waits for Firebase.initializeApp:

Firebase دەبێت یەک جار، پێش کارکردنی دەست پێ بکات. main دەبێت بە async، دڵنیا دەبێتەوە کە سیستەمی Flutter ئامادەیە و چاوەڕێی Firebase.initializeApp دەکات:

lib/main.dartDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart'; // written by `flutterfire configure`

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MaterialApp(home: Scaffold(body: Center(child: Text('Ready')))));
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 4

The file flutterfire configure wrote.

ئەو flutterfire configure نووسیویەتی.

Lineدێڕی 7

Needed before any plugin call that comes before runApp.

پێویستە پێش هەر بانگکردنێکی کە پێش runApp دێت.

Lineدێڕی 8-10

currentPlatform picks the Android, iOS or web settings automatically.

currentPlatform خۆکارانە Android، iOS یان وێب هەڵدەبژێرێت.

The generated file looks like this (shortened; the values here are placeholders):

دروستکراوەکە بەم شێوەیە دەردەکەوێت (کورتکراوەتەوە؛ نرخەکانی ئێرە ساختەن):

lib/firebase_options.dartDART
// A SHORTENED example of the file `flutterfire configure` writes.
// The values below are placeholders, not a real project.
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
    show defaultTargetPlatform, kIsWeb, TargetPlatform;

class DefaultFirebaseOptions {
  static FirebaseOptions get currentPlatform {
    if (kIsWeb) return web;
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        return android;
      case TargetPlatform.iOS:
        return ios;
      default:
        throw UnsupportedError('This platform is not configured.');
    }
  }

  static const FirebaseOptions android = FirebaseOptions(
    apiKey: 'YOUR-ANDROID-API-KEY',
    appId: '1:000000000000:android:0000000000000000',
    messagingSenderId: '000000000000',
    projectId: 'your-project-id',
  );

  static const FirebaseOptions ios = FirebaseOptions(
    apiKey: 'YOUR-IOS-API-KEY',
    appId: '1:000000000000:ios:0000000000000000',
    messagingSenderId: '000000000000',
    projectId: 'your-project-id',
    iosBundleId: 'com.example.students',
  );

  static const FirebaseOptions web = FirebaseOptions(
    apiKey: 'YOUR-WEB-API-KEY',
    appId: '1:000000000000:web:0000000000000000',
    messagingSenderId: '000000000000',
    projectId: 'your-project-id',
  );
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8-18

currentPlatform returns the options for the platform the app is running on.

currentPlatform ئەو دەگەڕێنێتەوە کە بۆ ئەو لەسەر کار دەکات.

Lineدێڕی 20-25

The apiKey, appId and projectId identify your Firebase project and app.

apiKey، appId و projectId پڕۆژە و Firebaseـەکەت دەناسێننەوە.

Quick checkپشکنینی خێراWhy is main marked async in a Firebase app?بۆچی لە Firebaseـدا main بە async نیشان دەکرێت؟

Flutter Firebase Setup🚀🔥 - Kurdish Tutorial | فێرکاری فڵەتەر بە کوردیFlutter Kurd Code · Kurdish (Sorani)کوردی (سۆرانی) · 763 viewsبینین · 43 likesلایک · Partبەشی 10:41–12:45 (ofلە 12:45)

In Kurdish: how a Flutter app starts Firebase in main() before runApp.

بە کوردی: چۆن Firebase لە main()ـدا پێش runApp دەست پێ دەکات.

Watch 10:41–12:45 (Kurdish): main() becomes async, WidgetsFlutterBinding.ensureInitialized() and await Firebase.initializeApp(). The video set Android up by hand, so it passes no options; after flutterfire configure, add options: DefaultFirebaseOptions.currentPlatform as this screen shows.

لە ⁦10:41⁩ تا ⁦12:45⁩ سەیر بکە (بە کوردی): main() دەبێتە async، WidgetsFlutterBinding.ensureInitialized() و await Firebase.initializeApp(). ڤیدیۆکە Android بە دەست ئامادە دەکات، بۆیە هیچ optionsـێک نانێرێت؛ دوای flutterfire configure، options: DefaultFirebaseOptions.currentPlatform زیاد بکە، وەک ئەم شاشەیە پیشانی دەدات.

Collections, documents and fieldsکۆکراوەکان، بەڵگەنامەکان و جووتەکانی کلیل و نرخ

Definitionپێناسە

A database that stores each record as a document, usually in or BSON, with its own fields. It suits catalogues, user profiles and content management systems.

جۆرێکە لە بنکەدراوەی کە هەر تۆمارێک وەک بەڵگەنامەیەک هەڵدەگرێت، زۆرجار بە یان BSON، لەگەڵ ستوونەکانی خۆی. بۆ کاتالۆگ، پرۆفایلی بەکارهێنەر و گونجاوە.

Cloud Firestore is a NoSQL, document-oriented database. Unlike an SQL database it has no tables and no rows. Data is stored in documents, and documents are grouped into collections. Each document is a small set of key–value pairs (fields), much like a JSON object. (The general idea of document databases is explained in Document databases.)

⁦Cloud Firestore⁩ ـە و . بە پێچەوانەی SQL، هیچ و نییە. دراوە لە هەڵدەگیرێت و لە کۆ دەکرێنەوە. هەر کۆمەڵەیەکی بچووکی ـە، زۆر وەک . (بیرۆکە گشتییەکەی لە بنکەدراوە بەڵگەنامەییەکان ڕوون کراوەتەوە.)

Left, a folder labelled students (a collection) holds three document cards with ids a7Kq2Wm9, Xb91pLzR and Q3eT0uvC. An arrow opens Xb91pLzR on the right: its fields are name "Shilan" (string), studentId 1021 (number), stage 3 (number), active true (boolean), subjects ["Flutter", "SQL"] (array) and joined, a timestamp; a small folder marks is a subcollection inside the document. Below: SQL table → row → column next to Firestore collection → document → field.لای چەپ فۆڵدەرێک بە ناوی students (کۆکراوەیەک) سێ کارتی بەڵگەنامە بە ناسێنەرەکانی a7Kq2Wm9، Xb91pLzR و Q3eT0uvC لەخۆ دەگرێت. تیرێک Xb91pLzR لای ڕاست دەکاتەوە: جووتەکانی کلیل و نرخی بریتین لە name "Shilan" (string)، studentId 1021 (number)، stage 3 (number)، active true (boolean)، subjects ["Flutter", "SQL"] (array) و joined کە کاتێکە؛ فۆڵدەرێکی بچووکی marks کۆکراوەیەکی ناوەکییە لەناو بەڵگەنامەکەدا. لە خوارەوە: SQL خشتە ← ڕیز ← ستوون لە تەنیشت Firestore کۆکراوە ← بەڵگەنامە ← جووتی کلیل و نرخ.
A collection of student documents, and one document opened. قوتابییان و یەک کە کراوەتەوە.CêIT
A school filing cabinetکەنتۆری فایلی قوتابخانە

Think of the school office. A drawer labelled Students is a collection. Each card in it is a document with its own number (the document id). The lines on a card, name, stage, phone, are the fields. You add a card, cross out a line and write a new one, or throw a card away, without touching the other cards.

بیر لە ژووری ئیدارەی قوتابخانە بکەرەوە. چەکمەجەیەک بە ناوی قوتابییان . هەر کارتێکی ناوی بە ژمارەی تایبەتی خۆی ( ). دێڕەکانی سەر کارتێک، ناو، قۆناغ، ژمارەی تەلەفۆن، . کارتێک زیاد دەکەیت، دێڕێک دەکوژێنیتەوە و یەکێکی نوێ دەنووسیت، یان کارتێک فڕێ دەدەیت، بەبێ دەستلێدان لە کارتەکانی تر.

Where the analogy stops workingسنووری ئەم نموونەیە

Unlike paper cards, two documents in one collection may have different fields (no fixed table shape), and a document can hold its own sub-drawers (subcollections). A document is limited to 1 MiB.

بە پێچەوانەی کارتی کاغەز، دوو لە یەک دەکرێت جیاوازیان هەبێت (شێوەی جێگیری نییە). هەروەها دەتوانێت چەکمەجەی ناوەکیی خۆی ( ناوەکی) هەبێت. قەبارەی سنووردارە بە ⁦1 MiB⁩.

Quick checkپشکنینی خێراIn Firestore, what plays the role of a row in an SQL table?لە Firestoreـدا چی ڕۆڵی لە SQLـدا دەبینێت؟

What is a NoSQL Database? How is Cloud Firestore structured? | Get to know Cloud Firestore #1Firebase · Englishئینگلیزی · 662K viewsبینین · 15K likesلایک · Partبەشی 9:58–15:29 (ofلە 15:29)

Firebase's own explanation of how Cloud Firestore stores data: documents of fields, grouped in collections, with subcollections.

Firebase خۆی ڕوون دەکاتەوە ⁦Cloud Firestore⁩ چۆن هەڵدەگرێت: لە پێک دێن، لە کۆ دەکرێنەوە و لاوەکییان هەیە.

Watch from 9:58: the document–collection model: collections hold only documents, documents hold fields and can have subcollections.

لە ⁦9:58⁩ـەوە سەیر بکە، شێوەی و : تەنها تێدایە، (fields) تێدایە و دەتوانن لاوەکییان (subcollections) هەبێت.

Reading live with StreamBuilderخوێندنەوەی ڕاستەوخۆ بە StreamBuilder

collection('students').snapshots() returns a Stream: the first value is the whole collection, and a new value arrives every time any document changes, on this phone or any other. A StreamBuilder (met in Streams and FutureBuilder and StreamBuilder) rebuilds the list for each value. Like FutureBuilder it must handle error, waiting and empty:

collection('students').snapshots() دەگەڕێنێتەوە: یەکەم نرخ هەموو و هەر جارێک هەر بگۆڕێت نرخێکی نوێ دێت، لەسەر ئەم مۆبایلە یان هەر مۆبایلێکی تر. StreamBuilder (کە لە تەوژمەکان و FutureBuilder و StreamBuilder ناسیت) بۆ هەر نرخێک دووبارە دروست دەکاتەوە. وەک FutureBuilder، دەبێت مامەڵە لەگەڵ ، چاوەڕوانی و بەتاڵ بکات:

lib/student.dartDART
import 'package:cloud_firestore/cloud_firestore.dart';

class Student {
  final String id; // the document id (not stored inside the document)
  final int studentId; // the university number, e.g. 1021
  final String name;
  final int stage;

  const Student({
    required this.id,
    required this.studentId,
    required this.name,
    required this.stage,
  });

  // A Firestore document -> a Student
  factory Student.fromDoc(DocumentSnapshot<Map<String, dynamic>> doc) {
    final data = doc.data()!;
    return Student(
      id: doc.id,
      studentId: data['studentId'] as int,
      name: data['name'] as String,
      stage: data['stage'] as int,
    );
  }

  // A Student -> the fields of a document
  Map<String, dynamic> toMap() =>
      {'studentId': studentId, 'name': name, 'stage': stage};
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 4

The document id lives outside the fields; we keep it so we can update or delete this document later.

لە دەرەوەی ؛ هەڵیدەگرین بۆ ئەوەی دواتر ئەم نوێ بکەینەوە یان بیسڕینەوە.

Lineدێڕی 17-25

fromDoc works like fromJson: one place that reads and checks every field (see A model class with fromJson).

fromDoc وەک fromJson کار دەکات: یەک شوێن کە هەموو دەخوێنێتەوە و دەپشکنێت (بڕوانە کلاسێکی مۆدێل بە fromJson).

Lineدێڕی 28-29

toMap gives the fields to write.

toMap ئەو دەدات کە دەنووسرێن.

A live list of studentsلیستێکی ڕاستەوخۆی قوتابییانDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

import 'student.dart';

class StudentList extends StatefulWidget {
  const StudentList({super.key});

  @override
  State<StudentList> createState() => _StudentListState();
}

class _StudentListState extends State<StudentList> {
  // Make the stream once, not in build().
  final Stream<QuerySnapshot<Map<String, dynamic>>> _students =
      FirebaseFirestore.instance
          .collection('students')
          .orderBy('name')
          .snapshots();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
      stream: _students,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Center(child: Text('Could not load: ${snapshot.error}'));
        }
        if (!snapshot.hasData) {
          return const Center(child: CircularProgressIndicator());
        }
        final docs = snapshot.data!.docs;
        if (docs.isEmpty) {
          return const Center(child: Text('No students yet.'));
        }
        return ListView.builder(
          itemCount: docs.length,
          itemBuilder: (context, index) {
            final student = Student.fromDoc(docs[index]);
            return ListTile(
              leading: CircleAvatar(child: Text('${student.stage}')),
              title: Text(student.name),
              subtitle: Text('ID ${student.studentId}'),
            );
          },
        );
      },
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 15-19

The stream is made once, as a field. orderBy('name') sorts on the server.

یەک جار وەک دروست دەکرێت. orderBy('name') لەسەر ڕیزیان دەکات.

Lineدێڕی 26-28

Error first, for example permission-denied from the security rules.

سەرەتا ، بۆ نموونە permission-denied لە ⁦Security Rules⁩ـەوە.

Lineدێڕی 29-31

No data yet: the first snapshot has not arrived. Show a spinner, never snapshot.data! here.

هێشتا دراوە نییە: یەکەم snapshot نەگەیشتووە. بازنەی چاوەڕوانی پیشان بدە، هەرگیز snapshot.data! لێرە نا.

Lineدێڕی 32-35

docs is the list of documents; an empty collection gets a friendly message.

docs ؛ بەتاڵ پەیامێکی دۆستانە وەردەگرێت.

Lineدێڕی 39

Each document becomes a typed Student.

هەر دەبێت بە Studentـێکی جۆردار.

Quick checkپشکنینی خێراWhat happens to the StreamBuilder list when another teacher adds a student on a different phone?کاتێک مامۆستایەکی تر لەسەر مۆبایلێکی تر قوتابییەک زیاد دەکات، StreamBuilder چی بەسەر دێت؟

Reading live with StreamBuilderخوێندنەوەی ڕاستەوخۆ بە StreamBuilder

Reading once with get()یەک جار خوێندنەوە بە get()

Not every screen needs live data. A report or an export can read the collection once with get(), which returns a Future<QuerySnapshot> (show it with a FutureBuilder). Use snapshots() when the screen should follow changes, get() when one picture is enough.

هەموو شاشەیەک پێویستی بە دراوەی ڕاستەوخۆ نییە. ڕاپۆرتێک یان دەرهێنانێک دەتوانێت یەک جار بە get() بخوێنێتەوە، کە Future<QuerySnapshot> دەگەڕێنێتەوە (بە FutureBuilder پیشانی بدە). کاتێک شاشەکە دەبێت بەدوای گۆڕانەکاندا بڕوات snapshots() بەکار بهێنە، کاتێک یەک وێنە بەسە get().

DART
Future<List<Student>> loadOnce() async {
  final query = await FirebaseFirestore.instance
      .collection('students')
      .orderBy('name')
      .get(); // one answer, no listener
  return query.docs.map(Student.fromDoc).toList();
}
Quick checkپشکنینی خێراA screen prints a monthly report from the students collection. Which call fits?شاشەیەک ڕاپۆرتێکی مانگانە لە students چاپ دەکات. کام بانگکردن گونجاوە؟

Create: add() and set()دروستکردن: add() و set()

There are two ways to put a new document into a collection. add(map) lets Firestore invent a unique document id and returns a reference to the new document. doc(id).set(map) writes a document whose id you choose; if a document with that id already exists, set replaces all of it.

دوو ڕێگا هەیە بۆ خستنە نێوی نوێ بۆ . add(map) ڕێگە دەدات Firestore تاک بۆ دروست بکات و ئاماژەیەک بۆ نوێیەکە دەگەڕێنێتەوە. doc(id).set(map) دەنووسێت کە تۆ هەڵدەبژێریت؛ ئەگەر بەو پێشتر هەبێت، set هەمووی دەگۆڕێتەوە.

A small Firestore serviceخزمەتگوزارییەکی بچووکی FirestoreDART
import 'package:cloud_firestore/cloud_firestore.dart';

import 'student.dart';

class StudentService {
  final _students = FirebaseFirestore.instance.collection('students');

  // CREATE with a generated id (the usual way)
  Future<String> add(Student s) async {
    final ref = await _students.add(s.toMap());
    return ref.id;
  }

  // CREATE or REPLACE a document whose id you choose
  Future<void> putWithId(String id, Student s) =>
      _students.doc(id).set(s.toMap());

  // UPDATE some fields; fails if the document does not exist
  Future<void> moveToStage(String id, int stage) =>
      _students.doc(id).update({'stage': stage});

  // MERGE: change some fields, create the document if it is missing
  Future<void> saveStage(String id, int stage) =>
      _students.doc(id).set({'stage': stage}, SetOptions(merge: true));

  // DELETE one field from a document
  Future<void> removeStage(String id) =>
      _students.doc(id).update({'stage': FieldValue.delete()});

  // DELETE the whole document
  Future<void> delete(String id) => _students.doc(id).delete();

  // READ once (no live updates)
  Future<List<Student>> loadOnce() async {
    final query = await _students.orderBy('name').get();
    return query.docs.map(Student.fromDoc).toList();
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6

A reference to the collection. Creating it costs nothing; no data is read yet.

ئاماژەیەک بۆ . دروستکردنی هیچ تێچووێکی نییە؛ هێشتا هیچ دراوەیەک ناخوێنرێتەوە.

Lineدێڕی 9-12

add returns a DocumentReference; ref.id is the generated id, for example pX3v9QkTq2LmZbW8aYcD.

add DocumentReferenceـێک دەگەڕێنێتەوە؛ ref.id دروستکراوەکەیە، بۆ نموونە pX3v9QkTq2LmZbW8aYcD.

Lineدێڕی 15-16

set with your own id: creates the document, or replaces it completely.

set بە خۆت: دروست دەکات، یان بە تەواوی دەیگۆڕێتەوە.

Lineدێڕی 19-20

update changes only the named fields (next screen).

update تەنها ئەو دەگۆڕێت کە ناویان هاتووە (شاشەی داهاتوو).

Lineدێڕی 23-24

set with merge: true mixes the new fields into the document, and creates it if it is missing.

set بە merge: true نوێیەکان تێکەڵ بە دەکات و ئەگەر نەبێت دروستی دەکات.

Lineدێڕی 27-28

FieldValue.delete() removes one field.

FieldValue.delete() یەک لادەبات.

Lineدێڕی 31

delete removes the whole document.

delete هەموو لادەبات.

Lineدێڕی 34-37

get reads once (see the previous screen).

get یەک جار دەخوێنێتەوە (بڕوانە شاشەی پێشوو).

Every call returns a Future. await it inside try, because a write can fail: no permission, a bad id, a quota. Firestore reports this as a FirebaseException with a short code:

هەموو بانگکردنێک دەگەڕێنێتەوە. لەناو tryـدا awaitـی بکە، چونکە نووسین دەکرێت شکست بهێنێت: نەبوونی مۆڵەت، هەڵە، تەواوبوونی بڕی بەکارهێنان. Firestore ئەمە وەک FirebaseException بە codeـێکی کورت ڕادەگەیەنێت:

DART
import 'package:cloud_firestore/cloud_firestore.dart';

Future<String> deleteStudent(String id) async {
  try {
    await FirebaseFirestore.instance.collection('students').doc(id).delete();
    return 'Deleted';
  } on FirebaseException catch (e) {
    // e.code is a short name such as 'permission-denied' or 'unavailable'
    return 'Could not delete (${e.code})';
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5

await inside try, so the failure arrives here instead of crashing the app.

await لەناو try، بۆ ئەوەی شکستەکە بگاتە ئێرە لە جیاتی تێکشکاندنی .

Lineدێڕی 7-9

e.code is easy to test: permission-denied, not-found, unavailable…

تاقیکردنەوەی e.code ئاسانە: permission-denied، not-found، unavailable…

Quick checkپشکنینی خێراYou call doc('aram').set({'name': 'Aram'}) on a document that already has name, stage and studentId. What is left?doc('aram').set({'name': 'Aram'}) بانگ دەکەیت لەسەر کە پێشتر name، stage و studentIdـی هەیە. چی دەمێنێتەوە؟

Create: add() and set()دروستکردن: add() و set()

Choosing document idsهەڵبژاردنی ناسێنەری بەڵگەنامە

The lecture's insert used the student's name as the document id: doc(studentName).set(...). Two students called Shilan then share one document, and saving the second one silently overwrites the first. Let Firestore make the id with add() (or doc() with no argument), and keep the id in your model so you can update and delete later.

insertـی وانەکە ناوی قوتابییەکەی وەک بەکار دەهێنا: doc(studentName).set(...). ئەو کاتە دوو قوتابی بە ناوی Shilan یەک بەکار دەهێنن و پاشەکەوتکردنی دووەمیان بە بێدەنگی یەکەمیان دەسڕێتەوە و جێی دەگرێتەوە. ڕێگە بدە Firestore بە add() (یان doc() بەبێ ) دروست بکات و لە هەڵبگرە بۆ ئەوەی دواتر نوێی بکەیتەوە و بیسڕیتەوە.

  • Generated id (add): the usual choice; 20 random characters, so two ids practically never clash.
  • Your own id (doc(id).set): only when the id is truly unique and stable, for example a user's sign-in uid, so users/{uid} belongs to exactly one person.
  • Never a name, a phone number or anything people share or change.
  • دروستکراو (add): هەڵبژاردنی ئاسایی؛ ⁦20⁩ پیتی هەڕەمەکی، بۆیە ئەگەری ئەوەی دوو وەک یەک دەربچن نزیکەی سفرە.
  • خۆت (doc(id).set): تەنها کاتێک بە ڕاستی تاک و جێگیرە، بۆ نموونە uidـی ، بۆ ئەوەی users/{uid} تەنها هی یەک کەس بێت.
  • هەرگیز ناو، ژمارەی تەلەفۆن یان هەر شتێک کە خەڵک هاوبەشی دەکەن یان دەیگۆڕن.
Quick checkپشکنینی خێراWhy is a student's name a bad document id?بۆچی ناوی قوتابی خراپە بۆ ؟

Update: update() or set with mergeنوێکردنەوە: update() یان set بە merge

Three ways to change a documentسێ ڕێگا بۆ گۆڕینی
CallبانگکردنIf the document existsئەگەر هەبێتIf it does not existئەگەر نەبێت
doc(id).set(map)Replaces all fields with mapهەموو بە map دەگۆڕێتەوەCreates itدروستی دەکات
doc(id).set(map, SetOptions(merge: true))Changes the fields in map, keeps the rest ناو map دەگۆڕێت، ئەوانی تر دەهێڵێتەوەCreates itدروستی دەکات
doc(id).update(map)Changes the fields in map, keeps the rest ناو map دەگۆڕێت، ئەوانی تر دەهێڵێتەوەFails (not-found)شکست دەهێنێت (not-found)

To move Shilan to stage 4, update({'stage': 4}) is the clearest: it touches one field and fails loudly if the document has been deleted meanwhile, instead of quietly creating a half-empty one. Use set with merge when "create it if it is missing" is really what you want, for example saving a user's settings document.

بۆ گواستنەوەی Shilan بۆ قۆناغی 4، update({'stage': 4}) ڕوونترینە: دەست لە یەک دەدات و ئەگەر لەو ماوەیەدا سڕابێتەوە بە ئاشکرا شکست دەهێنێت، لە جیاتی ئەوەی بە بێدەنگی یەکێکی نیوەبەتاڵ دروست بکات. کاتێک «ئەگەر نییە دروستی بکە» بە ڕاستی ئەوەیە کە دەتەوێت set بە merge بەکار بهێنە، بۆ نموونە پاشەکەوتکردنی .

Quick checkپشکنینی خێراA document might not exist yet, and you want to save one field without erasing others. Which call? لەوانەیە هێشتا نەبێت و دەتەوێت یەک پاشەکەوت بکەیت بەبێ سڕینەوەی ئەوانی تر. کام بانگکردن؟

Delete: delete()سڕینەوە: delete()

doc(id).delete() removes one whole document. To remove only one field, update it with FieldValue.delete(). Two rules from the Firestore docs: deleting a document does not delete the documents in its subcollections, and deleting a whole collection from an app is not recommended (do it from a trusted server or the console).

doc(id).delete() بە تەواوی لادەبات. بۆ لابردنی تەنها یەک ، بە FieldValue.delete() نوێی بکەرەوە. دوو یاسا لە بەڵگەنامەکانی Firestoreـەوە: سڕینەوەی ناو ناوەکییەکانی ناسڕێتەوە. هەروەها سڕینەوەی هەموو لە پێشنیار ناکرێت (لە متمانەپێکراو یان لە کۆنسۆڵەوە بیکە).

Quick checkپشکنینی خێراHow do you remove only the stage field from a student document?چۆن تەنها stage لە قوتابییەک لادەبەیت؟

The student app, put togetherئەپی قوتابییان، بە تەواوی

Here is the lecture's student app rebuilt with everything from this lesson: a form with three fields (ID, name, stage) and an Add/Update button, and a live list below it. Tap a student to edit, tap the bin to delete. The app never calls setState for the list: every write changes Firestore, and the stream redraws the list, on this phone and on every other phone watching.

ئەمە قوتابییانی وانەکەیە کە بە هەموو ئەوەی لەم وانەیەدا هەیە دووبارە دروست کراوەتەوە: بە سێ خانە (ID، ناو، قۆناغ) و Add/Update، لەگەڵ ڕاستەوخۆ لە خوارەوە. بۆ دەستکاری لە قوتابییەک بدە، بۆ سڕینەوە لە سەتڵەکە بدە. هەرگیز بۆ setState بانگ ناکات: هەموو نووسینێک Firestore دەگۆڕێت و دووبارە دەکێشێتەوە، لەسەر ئەم مۆبایلە و لەسەر هەموو مۆبایلێکی تر کە سەیری دەکات.

Students: live CRUD with Firestoreقوتابییان: CRUDـی ڕاستەوخۆ بە FirestoreDARTRuns on a phone (or an emulator)لەسەر مۆبایل کار دەکات
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'student.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const MaterialApp(home: StudentsScreen()));
}

class StudentsScreen extends StatefulWidget {
  const StudentsScreen({super.key});

  @override
  State<StudentsScreen> createState() => _StudentsScreenState();
}

class _StudentsScreenState extends State<StudentsScreen> {
  final _students = FirebaseFirestore.instance.collection('students');
  late final Stream<QuerySnapshot<Map<String, dynamic>>> _stream =
      _students.orderBy('name').snapshots();

  final _idField = TextEditingController();
  final _nameField = TextEditingController();
  final _stageField = TextEditingController();
  String? _editingId; // null: the form adds a new student

  @override
  void dispose() {
    _idField.dispose();
    _nameField.dispose();
    _stageField.dispose();
    super.dispose();
  }

  Future<void> _save() async {
    final studentId = int.tryParse(_idField.text);
    final stage = int.tryParse(_stageField.text);
    final name = _nameField.text.trim();
    if (studentId == null || stage == null || name.isEmpty) {
      _show('Fill in all three fields correctly.');
      return;
    }
    final data = {'studentId': studentId, 'name': name, 'stage': stage};
    final id = _editingId;
    try {
      if (id == null) {
        await _students.add(data); // Create: Firestore makes the id
      } else {
        await _students.doc(id).update(data); // Update: only these fields
      }
      _clearForm();
    } on FirebaseException catch (e) {
      _show('Could not save (${e.code})');
    }
  }

  Future<void> _delete(String id) async {
    try {
      await _students.doc(id).delete(); // Delete the whole document
    } on FirebaseException catch (e) {
      _show('Could not delete (${e.code})');
    }
  }

  void _startEditing(Student s) {
    setState(() => _editingId = s.id);
    _idField.text = '${s.studentId}';
    _nameField.text = s.name;
    _stageField.text = '${s.stage}';
  }

  void _clearForm() {
    setState(() => _editingId = null);
    _idField.clear();
    _nameField.clear();
    _stageField.clear();
  }

  void _show(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Students')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              children: [
                TextField(
                  controller: _idField,
                  keyboardType: TextInputType.number,
                  decoration: const InputDecoration(labelText: 'Student ID'),
                ),
                TextField(
                  controller: _nameField,
                  decoration: const InputDecoration(labelText: 'Name'),
                ),
                TextField(
                  controller: _stageField,
                  keyboardType: TextInputType.number,
                  decoration: const InputDecoration(labelText: 'Stage'),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    FilledButton(
                      onPressed: _save,
                      child: Text(_editingId == null ? 'Add' : 'Update'),
                    ),
                    if (_editingId != null)
                      TextButton(onPressed: _clearForm, child: const Text('Cancel')),
                  ],
                ),
              ],
            ),
          ),
          const Divider(),
          Expanded(
            child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
              stream: _stream, // Read: live, rebuilt on every change
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Center(child: Text('Error: ${snapshot.error}'));
                }
                if (!snapshot.hasData) {
                  return const Center(child: CircularProgressIndicator());
                }
                final students =
                    snapshot.data!.docs.map(Student.fromDoc).toList();
                if (students.isEmpty) {
                  return const Center(child: Text('No students yet.'));
                }
                return ListView(
                  children: [
                    for (final s in students)
                      ListTile(
                        leading: CircleAvatar(child: Text('${s.stage}')),
                        title: Text(s.name),
                        subtitle: Text('ID ${s.studentId}'),
                        onTap: () => _startEditing(s),
                        trailing: IconButton(
                          icon: const Icon(Icons.delete_outline),
                          tooltip: 'Delete',
                          onPressed: () => _delete(s.id),
                        ),
                      ),
                  ],
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8-12

Firebase starts before the app.

Firebase پێش دەست پێ دەکات.

Lineدێڕی 22-24

One collection reference and one stream, made once.

یەک ئاماژە بۆ و یەک ، یەک جار دروست دەکرێن.

Lineدێڕی 26-37

Text field controllers are disposed with the State: see the lifecycle of a State object and TextEditingController.

کۆنترۆڵەرەکانی لەگەڵ Stateـەکە فڕێ دەدرێن: بڕوانە سووڕی ژیانی ئۆبجێکتی دۆخ و TextEditingController.

Lineدێڕی 39-46

Check the input before writing anything.

پێش نووسینی هەر شتێک بپشکنە.

Lineدێڕی 48-58

No id yet → add (Create); editing → update (Update). Errors become a SnackBar.

هێشتا نییە ← add (دروستکردن)؛ دەستکاری ← update (نوێکردنەوە). دەبن بە SnackBar.

Lineدێڕی 61-67

delete removes the document (Delete).

delete لادەبات (سڕینەوە).

Lineدێڕی 129-160

The StreamBuilder (Read): error, waiting, empty, then the list; onTap starts editing and the bin deletes.

StreamBuilder (خوێندنەوە): ، چاوەڕوانی، بەتاڵ، پاشان ؛ onTap دەستکاری دەست پێ دەکات و سەتڵەکە دەسڕێتەوە.

Animation in four frames: a phone titled Students next to a Firestore panel showing the students collection. 1: the list shows Aram (stage 2) and Shilan (stage 3), matching two documents; label 'Read: snapshots() streams the collection into the list'. 2: Karwan (stage 1) appears in the list and as a new highlighted document after add(); label 'Create'. 3: Shilan's stage changes from 3 to 4 in the document and in the list after update({'stage': 4}); label 'Update'. 4: Aram is crossed out in the list and his document is marked deleted after delete(); label 'Delete'.
Animation in four frames: a phone titled Students next to a Firestore panel showing the students collection. 1: the list shows Aram (stage 2) and Shilan (stage 3), matching two documents; label 'Read: snapshots() streams the collection into the list'. 2: Karwan (stage 1) appears in the list and as a new highlighted document after add(); label 'Create'. 3: Shilan's stage changes from 3 to 4 in the document and in the list after update({'stage': 4}); label 'Update'. 4: Aram is crossed out in the list and his document is marked deleted after delete(); label 'Delete'.
Each write changes Firestore; the stream redraws the list by itself.هەر نووسینێک Firestore دەگۆڕێت؛ خۆی دووبارە دەکێشێتەوە.CêIT
Quick checkپشکنینی خێراAfter _students.add(data) succeeds, how does the new student appear in the list?دوای سەرکەوتنی _students.add(data)، قوتابییە نوێیەکە چۆن لە دەردەکەوێت؟

Security Rules: who may read and write⁦Security Rules⁩: کێ دەتوانێت بخوێنێتەوە و بنووسێت

Your app talks to Firestore directly, and the project settings are inside every copy of the app. So anyone could write a program that calls your database. What stops them is Cloud Firestore Security Rules: rules stored on Firebase's servers that decide, for every request, whether it may read or write. They are access control done on the server, where the user cannot change it.

ڕاستەوخۆ قسە لەگەڵ Firestore دەکات و پڕۆژەکە لەناو هەموو کۆپییەکی هەن. بۆیە هەر کەسێک دەتوانێت بەرنامەیەک بنووسێت کە بانگ بکات. ئەوەی ڕێگرییان لێ دەکات ⁦Cloud Firestore Security Rules⁩ـە: یاساگەلێک کە لەسەر Firebase هەڵگیراون و بۆ هەر بڕیار دەدەن ئایا دەتوانێت بخوێنێتەوە یان بنووسێت. ئەمانە کۆنترۆڵی دەستڕاگەیشتنـن کە لەسەر ئەنجام دەدرێن، لە شوێنێک کە ناتوانێت بیگۆڕێت.

When you create the database, Firebase asks for test mode or production mode. Test mode lets anyone read and overwrite your data: fine for a first try while learning, dangerous for anything real. Production mode denies all access from apps until you write rules. Two common patterns (Firebase Authentication provides request.auth):

کاتێک دروست دەکەیت، Firebase دەپرسێت ⁦test mode⁩ یان ⁦production mode⁩. ⁦test mode⁩ ڕێگە دەدات هەر کەسێک دراوەکەت بخوێنێتەوە و بیگۆڕێت: بۆ یەکەم تاقیکردنەوەکانی فێربوون باشە، بۆ هەر شتێکی ڕاستەقینە مەترسیدارە. ⁦production mode⁩ هەموو دەستڕاگەیشتنێک لە ڕەت دەکاتەوە تا یاساکان دەنووسیت. دوو شێوازی باو (⁦Firebase Authentication⁩ request.auth دابین دەکات):

Firestore Security Rules (Firebase console → Firestore → Rules)⁦Firestore Security Rules⁩ (کۆنسۆڵی Firebase → Firestore → Rules)TEXT
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Any signed-in user may read and write students
    match /students/{studentId} {
      allow read, write: if request.auth != null;
    }

    // Each user may read and write only their own profile
    match /users/{userId} {
      allow read, write: if request.auth != null
                         && request.auth.uid == userId;
    }
  }
}
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6-8

request.auth != null: only users who signed in with Firebase Authentication.

request.auth != null: تەنها ئەو بە ⁦Firebase Authentication⁩ چوونەتە ژوورەوە.

Lineدێڕی 11-14

{userId} is the document id; it must equal the signed-in user's uid, so nobody can open another person's profile.

{userId} ؛ دەبێت یەکسان بێت بە uidـی ئەو چووەتە ژوورەوە، بۆیە هیچ کەسێک ناتوانێت پرۆفایلی کەسێکی تر بکاتەوە.

Quick checkپشکنینی خێراWhat really protects a Firestore database from strangers?بە ڕاستی چی Firestore لە کەسانی نەناسراو دەپارێزێت؟

Introduction to Firebase Security Rules - FirecastsFirebase · Englishئینگلیزی · 66K viewsبینین · 1.3K likesلایک · Partبەشی 0:00–3:49 (ofلە 7:07)

What Firebase Security Rules are, how a match and allow rule is written, and how to allow access only to signed-in users.

Firebase Security Rules چییە، یاسایەک بە match و allow چۆن دەنووسرێت و چۆن تەنها ئەو چوونەتە ژوورەوە دەستیان بە بگات.

Watch 0:00–3:49: why rules sit between the app and the data, the syntax for Firestore (0:58) and rules based on the signed-in user or the data (2:35). Stops before the 2019 limitations.

لە ⁦0:00⁩ تا ⁦3:49⁩ سەیر بکە: بۆچی یاساکان لە نێوان و دادەنرێن، یاساکانی ⁦Firestore (0:58)⁩ و یاسا بەپێی ئەو چووەتە ژوورەوە یان بەپێی ⁦(2:35)⁩. پێش بەشی کەموکوڕییەکانی ⁦2019⁩ ڕادەوەستێت.

Common mistakesهەڵە باوەکان

MistakeهەڵەWhat happensچی ڕوو دەداتFixچاکردنەوە
Using Firestore before Firebase.initializeAppبەکارهێنانی Firestore پێش Firebase.initializeAppError: no Firebase App '[DEFAULT]' has been created: ⁦no Firebase App '[DEFAULT]' has been created⁩await Firebase.initializeApp(...) in main before runAppawait Firebase.initializeApp(...) لە mainـدا پێش runApp
snapshot.data! before checkingsnapshot.data! پێش پشکنینRed error screen while loadingشاشەی سووری لە کاتی بارکردنداCheck hasError, then hasDataسەرەتا hasError، پاشان hasData بپشکنە
set to change one fieldset بۆ گۆڕینی یەک The other fields disappear تر ون دەبنupdate, or set with merge: trueupdate، یان set بە merge: true
set (or a copy of insert) to deleteset (یان کۆپییەکی insert) بۆ سڕینەوەThe document is written again, not removed دووبارە دەنووسرێتەوە، لانابرێتdoc(id).delete()doc(id).delete()
A name as the document idناو وەک Same-name students overwrite each otherقوتابییانی هاوناو یەکتر دەسڕنەوەadd() and keep doc.idadd() و هەڵگرتنی doc.id
Creating the stream inside buildدروستکردنی لەناو buildA new listener on every rebuild نوێ لە هەر دووبارە دروستکردنەوەیەکداKeep it in a field of the Stateلە Stateـدا هەڵیبگرە
Not awaiting writes, no tryawait نەکردنی نووسینەکان، بێ tryFailures are silentشکستەکان بێدەنگنawait inside try / on FirebaseExceptionawait لەناو try / on FirebaseException
Leaving test-mode rules onهێشتنەوەی یاساکانی ⁦test mode⁩Anyone can read and overwrite everythingهەر کەسێک دەتوانێت هەموو شتێک بخوێنێتەوە و بیگۆڕێتWrite real Security Rules before releaseپێش بڵاوکردنەوە ⁦Security Rules⁩ـی ڕاستەقینە بنووسە

Exercisesڕاهێنانەکان

Exerciseڕاهێنان1. Design a books collection١. کۆکراوەیەکی کتێب دیزاین بکە

A school library app stores books. Design the fields of one document in a books collection: name, type and an example value for each. Which value should be the document id?

کتێبخانەی قوتابخانە کتێب هەڵدەگرێت. یەک لە books دیزاین بکە: ناو، جۆر و نرخێکی نموونە بۆ هەر یەکێکیان. کام نرخ دەبێت بێت؟

Show answerوەڵامەکە پیشان بدە
FieldTypeجۆرExampleنموونە
titlestring"Kurdish Grammar"
authorstring"Hana"
pagesnumber240
availablebooleantrue
addedAttimestampFieldValue.serverTimestamp()

Let Firestore generate the id with add(): titles repeat (two copies of one book) and can be corrected.

ڕێگە بدە Firestore بە add() دروست بکات: ناونیشانەکان دووبارە دەبنەوە (دوو دانە لە یەک کتێب) و دەکرێت چاک بکرێن.

Exerciseڕاهێنان2. Add, lend and list books٢. زیادکردن، بەقەرزدان و لیستکردنی کتێب

Write addBook (returns the new id, stores the server time), lendBook(id) (marks it not available) and availableTitles() (a live stream of titles of available books).

addBook (ناسێنەرە نوێیەکە دەگەڕێنێتەوە و کاتی هەڵدەگرێت)، lendBook(id) (وەک بەردەست نەبوو نیشانی دەکات) و availableTitles() ( ڕاستەوخۆی ناونیشانی کتێبە بەردەستەکان) بنووسە.

Show answerوەڵامەکە پیشان بدە
DART
import 'package:cloud_firestore/cloud_firestore.dart';

final books = FirebaseFirestore.instance.collection('books');

Future<String> addBook(String title, String author, int pages) async {
  final ref = await books.add({
    'title': title,
    'author': author,
    'pages': pages,
    'available': true,
    'addedAt': FieldValue.serverTimestamp(), // the server's clock
  });
  return ref.id; // e.g. 'pX3v9QkTq2LmZbW8aYcD'
}

Future<void> lendBook(String id) =>
    books.doc(id).update({'available': false});

Stream<List<String>> availableTitles() => books
    .where('available', isEqualTo: true)
    .snapshots()
    .map((query) =>
        query.docs.map((doc) => doc.data()['title'] as String).toList());

FieldValue.serverTimestamp() uses Firebase's clock, not the phone's (which may be wrong). where(...).snapshots().map(...) turns a live query into a live list of strings.

FieldValue.serverTimestamp() کاتژمێری Firebase بەکار دەهێنێت، نەک هی مۆبایلەکە (کە دەکرێت هەڵە بێت). where(...).snapshots().map(...) ڕاستەوخۆ دەکات بە ڕاستەوخۆی .

Exerciseڕاهێنان3. set, merge or update?٣. set، merge یان update؟

Choose the call for each: (a) a settings document settings/{uid} that may not exist yet, saving only language; (b) moving an existing student to stage 3; (c) replacing a draft with a completely new version; (d) a new chat message.

بۆ هەر یەکێک بانگکردنەکە هەڵبژێرە: (أ) ڕێکخستنەکان settings/{uid} کە لەوانەیە هێشتا نەبێت، تەنها language پاشەکەوت دەکەیت؛ (ب) گواستنەوەی قوتابییەکی هەبوو بۆ قۆناغی 3؛ (ج) گۆڕینی ڕەشنووسێک بە وەشانێکی تەواو نوێ؛ (د) نامەیەکی نوێی چات.

Show answerوەڵامەکە پیشان بدە
  • (a) set({'language': 'ckb'}, SetOptions(merge: true))
  • (b) update({'stage': 3})
  • (c) set(newDraft) (replacing everything is the goal)
  • (d) add(message) with a generated id
  • (أ) بە set({'language': 'ckb'}, SetOptions(merge: true))
  • (ب) بە update({'stage': 3})
  • (ج) set(newDraft) (گۆڕینی هەموو شتێک ئامانجەکەیە)
  • (د) add(message) بە دروستکراو
Exerciseڕاهێنان4. Fix the lecture's functions٤. کردارەکانی وانەکە چاک بکەرەوە

The lecture's insert, update and delete all used doc(studentName).set(...). Rewrite the three so that students with the same name never clash, update changes only the stage, and delete really deletes.

insert، update و deleteـی وانەکە هەموویان doc(studentName).set(...)ـیان بەکار دەهێنا. هەر سێکیان دووبارە بنووسەوە بۆ ئەوەی قوتابییانی هاوناو هەرگیز بەریەک نەکەون، update تەنها قۆناغ بگۆڕێت و delete بە ڕاستی بسڕێتەوە.

Show answerوەڵامەکە پیشان بدە

See the corrected code in Delete: add for insert, doc(docId).update({'studentStage': …}) for update, doc(docId).delete() for delete, each awaited.

کۆدە چاککراوەکە لە سڕینەوە ببینە: add بۆ insert، doc(docId).update({'studentStage': …}) بۆ update، doc(docId).delete() بۆ delete، هەر یەکەیان بە await.

Exerciseڕاهێنان5. Write a rule٥. یاسایەک بنووسە

Write a Security Rule so that any signed-in user can read the books collection, but nobody can write to it from the app (the librarian uses the console).

⁦Security Rule⁩ـێک بنووسە بۆ ئەوەی هەر کە چووەتە ژوورەوە بتوانێت books بخوێنێتەوە، بەڵام هیچ کەسێک لە نەتوانێت تێیدا بنووسێت (کتێبدار کۆنسۆڵ بەکار دەهێنێت).

Show answerوەڵامەکە پیشان بدە
TEXT
match /books/{bookId} {
  allow read: if request.auth != null;
  allow write: if false;
}

Read and write are separate permissions. if false closes writing completely; console and server access are not affected by rules.

خوێندنەوە و نووسین دوو مۆڵەتی جیان. if false نووسین بە تەواوی دادەخات؛ کۆنسۆڵ و دەستڕاگەیشتنی بە یاساکان کاریگەر نابن.

Quick checkپشکنینی خێراWhich call creates a document with an id that Firestore generates?کام بانگکردن بە دروست دەکات کە Firestore دروستی دەکات؟

Summaryپوختە

  • FlutterFire = the official Flutter plugins for Firebase. Set up with firebase login, flutterfire configure (writes firebase_options.dart) and flutter pub add firebase_core cloud_firestore.
  • Start it once in main: await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).
  • Cloud Firestore is a NoSQL document database: collections of documents, each a set of key–value fields; no tables, no rows.
  • Read live with snapshots() + StreamBuilder (check error and loading first), or once with get().
  • Create with add() (generated id) or doc(id).set(); update with update() or set(..., SetOptions(merge: true)); delete with delete(), never with set.
  • Protect the data with Security Rules, not by hiding the configuration; never ship test-mode rules.
  • FlutterFire = فەرمییەکانی Flutter بۆ Firebase. بە firebase login، flutterfire configure (firebase_options.dart دەنووسێت) و flutter pub add firebase_core cloud_firestore ئامادەی بکە.
  • یەک جار لە mainـدا دەستی پێ بکە: await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).
  • ⁦Cloud Firestore⁩ لە جۆری : کە لەخۆ دەگرن، هەر یەکەیان کۆمەڵەیەک ؛ نە ، نە .
  • بە snapshots() + StreamBuilder بە شێوەی ڕاستەوخۆ بخوێنەرەوە (سەرەتا و چاوەڕوانی بپشکنە)، یان بە get() یەک جار.
  • بە add() ( دروستکراو) یان doc(id).set() دروست بکە؛ بە update() یان set(..., SetOptions(merge: true)) نوێ بکەرەوە؛ بە delete() بسڕەوە، هەرگیز بە set نا.
  • دراوەکە بە ⁦Security Rules⁩ بپارێزە، نەک بە شاردنەوەی ؛ هەرگیز یاساکانی ⁦test mode⁩ لەگەڵ بڵاو مەکەرەوە.
Review questionsپرسیارەکانی پێداچوونەوە
What is the difference between add() and doc(id).set()?جیاوازیی نێوان add() و doc(id).set() چییە؟

add creates a document with a generated id. set writes the document with the id you give, creating it or replacing it completely.

add بە دروستکراو دروست دەکات. set بەو تۆ دەیدەیت دەنووسێت، دروستی دەکات یان بە تەواوی دەیگۆڕێتەوە.

Why must a StreamBuilder check hasData before snapshot.data!?بۆچی StreamBuilder دەبێت پێش snapshot.data! hasData بپشکنێت؟

Until the first snapshot arrives, data is null, and the ! would throw.

تا یەکەم snapshot دەگات، دراوەکە nullـە و ! فڕێ دەدات.

Is the Firebase API key in firebase_options.dart a secret?ئایا کلیلی APIـی Firebase لە firebase_options.dartـدا نهێنییە؟

No: it is an identifier inside every copy of the app. Security Rules protect the data. Still, do not publish a real project's keys.

نەخێر: کە لەناو هەموو کۆپییەکی . ⁦Security Rules⁩ دراوەکە دەپارێزن. لەگەڵ ئەوەشدا پڕۆژەیەکی ڕاستەقینە بڵاو مەکەرەوە.

Videoڤیدیۆ

Watch a videoسەیری ڤیدیۆیەک بکە

A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.

🔥📱 Flutter x Firebase CRUD Masterclass • Create / Read / Update / DeleteMitch Koko · Englishئینگلیزی · 100K viewsبینین · 2.2K likesلایک

A notes app on Cloud Firestore: set up Firebase with FlutterFire, then create, read live, update and delete documents.

نووسینی تێبینی لەسەر ⁦Cloud Firestore⁩: ئامادەکردنی Firebase بە FlutterFire، پاشان دروستکردن، خوێندنەوەی ڕاستەوخۆ، و سڕینەوەی ().

The whole 13-minute video: setup with firebase login and flutterfire configure (0:00), add (4:36), read (7:33), update (10:12) and delete (12:13). At 2:40 it opens the rules so anyone can write: fine only while learning, see Security Rules.

هەموو ڤیدیۆ ⁦13⁩ خولەکییەکە: ئامادەکردن بە firebase login و flutterfire configure ⁦(0:00)⁩، زیادکردن ⁦(4:36)⁩، خوێندنەوە ⁦(7:33)⁩، ⁦(10:12)⁩ و سڕینەوە ⁦(12:13)⁩. لە ⁦2:40⁩دا یاساکان دەکاتەوە تا هەر کەسێک بتوانێت بنووسێت: ئەمە تەنها لە کاتی فێربووندا باشە؛ بڕوانە بەشی Security Rules.

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

State Management with Providerبەڕێوەبردنی دۆخ بە Provider

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.