# Što ćete izgraditi (i zašto je to važno)#
Ovaj flutter firebase tutorial vodi vas od praznog Flutter projekta do deployane aplikacije s Firebase Authentication, Firestore i Cloud Functions. Na kraju ćete imati aplikaciju koja može prijaviti korisnike, sigurno spremati podatke po korisniku i pokretati serversku logiku kojoj ne možete vjerovati na klijentu.
Firebase je popularan jer uklanja mjesece backend posla, ali produkcijske aplikacije i dalje pucaju na osnovama poput security rulesa, indeksiranja i modeliranja podataka. Ovaj vodič fokusira se na praktične stvari koje su bitne za stvarne deploye, a ne samo na “radi na mom mobitelu”.
Ako procjenjujete cross-platform opcije, usporedite kompromisa u Flutter vs React Native (2026). Ako trebate tim koji isporučuje brže, Samioda gradi produkcijske aplikacije s Flutterom i automation stackovima: mobile & web development.
# Preduvjeti (alati + računi)#
| Zahtjev | Verzija (preporučeno) | Napomene |
|---|---|---|
| Flutter SDK | Stable channel (latest) | flutter doctor treba biti “green” |
| Dart | Dolazi s Flutterom | Nema potrebe za zasebnom instalacijom |
| Android Studio / Xcode | Najnoviji stable | Za emulatore/simulatore |
| Firebase račun | Google račun | Napravit ćete projekt + aplikacije |
| Node.js | 18+ LTS | Potrebno za Firebase CLI + Functions |
| Firebase CLI | Najnoviji | Deploy Functions, konfiguracija hostinga (opcionalno) |
Instalirajte Firebase CLI:
npm i -g firebase-tools
firebase loginKreirajte i provjerite svoju Flutter aplikaciju:
flutter create flutter_firebase_2026
cd flutter_firebase_2026
flutter runℹ️ Napomena: Ovaj tutorial koristi moderni Firebase pristup u Flutteru kroz
flutterfireCLI za generiranje platformskih konfiguracija. Time se izbjegavaju ručne greške u konfiguraciji i tajne se ne guraju u source control.
# Korak 1: Kreirajte Firebase projekt + registrirajte aplikacije#
1) Kreirajte Firebase projekt#
- 1Idite na Firebase Console → Add project
- 2Odaberite naziv projekta (npr.
flutter-firebase-2026) - 3Uključite Google Analytics samo ako vam treba odmah (možete dodati kasnije)
2) Registrirajte Android i iOS aplikacije#
U Firebase projektu:
- Dodajte Android app s package nameom poput
com.example.flutter_firebase_2026 - Dodajte iOS app s bundle ID-jem poput
com.example.flutterFirebase2026
Za Android, postavite applicationId u android/app/build.gradle (ako želite prilagođeni ID). Za iOS, postavite bundle ID u Xcodeu (Runner target settings) ili u predlošku projekta.
3) Konfigurirajte putem FlutterFire CLI-a#
Instalirajte FlutterFire CLI:
dart pub global activate flutterfire_cliPokrenite konfiguraciju:
flutterfire configureOvo generira lib/firebase_options.dart i dodaje native konfiguracijske datoteke na prava mjesta.
# Korak 2: Dodajte Firebase ovisnosti u Flutter#
Dodajte pakete:
flutter pub add firebase_core firebase_auth cloud_firestore
flutter pub add flutter_riverpodInicijalizirajte Firebase u main.dart:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: Center(child: Text('Firebase ready'))),
);
}
}U ovom trenutku pokrenite:
flutter runAko vidite “Firebase ready”, završili ste osnovnu postavu.
💡 Savjet: Dodajte “smoke test” u ovoj fazi (jedan uređaj + jedan emulator). Jeftinije je popraviti native konfiguracijske probleme sada nego nakon što dodate auth, rules i functions.
# Korak 3: Firebase Authentication (Email/Password)#
Autentifikacija je temelj kontrole pristupa. Bez autha Firestore rulesi često postanu previše permisivni “samo za testiranje” i slučajno odu u produkciju.
1) Omogućite metode prijave#
Firebase Console → Authentication → Sign-in method:
- Omogućite Email/Password
2) Implementirajte minimalni auth servis#
Kreirajte lib/auth/auth_service.dart:
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream<User?> authStateChanges() => _auth.authStateChanges();
Future<UserCredential> signUp(String email, String password) {
return _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
}
Future<UserCredential> signIn(String email, String password) {
return _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
}
Future<void> signOut() => _auth.signOut();
}3) Izgradite jednostavan UI: sign-in / sign-up#
Kreirajte lib/auth/auth_screen.dart:
import 'package:flutter/material.dart';
import 'auth_service.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = AuthService();
final _email = TextEditingController();
final _password = TextEditingController();
String? _error;
Future<void> _run(Future<void> Function() action) async {
setState(() => _error = null);
try {
await action();
} catch (e) {
setState(() => _error = e.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign in')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(controller: _email, decoration: const InputDecoration(labelText: 'Email')),
TextField(
controller: _password,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 12),
if (_error != null) Text(_error!, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _run(() async {
await _auth.signIn(_email.text.trim(), _password.text.trim());
}),
child: const Text('Sign in'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton(
onPressed: () => _run(() async {
await _auth.signUp(_email.text.trim(), _password.text.trim());
}),
child: const Text('Sign up'),
),
),
],
),
],
),
),
);
}
}4) Rutirajte korisnike prema auth statusu#
Zamijenite home: u MyApp s auth gateom.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'auth/auth_screen.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
if (snapshot.data == null) return const AuthScreen();
return const Scaffold(body: Center(child: Text('Logged in')));
},
);
}
}I postavite:
home: const AuthGate(),⚠️ Upozorenje: Ne prikazujte “logged in” sadržaj prije nego što se
authStateChanges()razriješi. Inače ćete dobiti “flicker” i, još gore, nakratko renderirati ekrane koji pretpostavljaju da korisnik postoji.
# Korak 4: Firestore model podataka + sigurnosna pravila#
Firestore je fleksibilan, ali fleksibilnost stvara neuredne sheme. Krenite s čistim, minimalnim modelom.
Model podataka: “todos” po korisniku#
Todos ćemo spremati pod korisnika:
users/{uid}(profil)users/{uid}/todos/{todoId}(stavke)
Ova struktura čini security rules jednostavnima: korisnik može pristupiti samo svom podstablu.
| Kolekcija/Dokument | Putanja | Primjer polja |
|---|---|---|
| Korisnički profil | users/{uid} | email, createdAt |
| Todo stavka | users/{uid}/todos/{todoId} | title, done, createdAt, updatedAt |
1) Kreirajte Firestore bazu#
Firebase Console → Firestore Database → Create database. Odaberite regiju blizu korisnika (latencija je bitna; čak i 80–150ms po requestu se zbroji).
2) Postavite Firestore security rules (produkcijska osnova)#
Firestore → Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /users/{userId} {
allow read, write: if isSignedIn() && request.auth.uid == userId;
match /todos/{todoId} {
allow read, write: if isSignedIn() && request.auth.uid == userId;
}
}
}
}Ovo zaključava podatke na autentificiranog korisnika. Nije “enterprise RBAC”, ali sprječava najčešći Firebase breach: globalni read pristup.
3) Izgradite Firestore CRUD servis#
Kreirajte lib/data/todo_service.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class TodoService {
final _db = FirebaseFirestore.instance;
String get _uid => FirebaseAuth.instance.currentUser!.uid;
CollectionReference<Map<String, dynamic>> get _todos =>
_db.collection('users').doc(_uid).collection('todos');
Stream<QuerySnapshot<Map<String, dynamic>>> watchTodos() {
return _todos.orderBy('createdAt', descending: true).snapshots();
}
Future<void> addTodo(String title) {
final now = FieldValue.serverTimestamp();
return _todos.add({
'title': title,
'done': false,
'createdAt': now,
'updatedAt': now,
});
}
Future<void> toggleDone(String id, bool done) {
return _todos.doc(id).update({
'done': done,
'updatedAt': FieldValue.serverTimestamp(),
});
}
Future<void> deleteTodo(String id) => _todos.doc(id).delete();
}🎯 Ključna poruka: Koristite
serverTimestamp()za audit polja. Klijentski timestampi driftaju (i mogu se manipulirati), što kvari sortiranje i može uzrokovati zbunjujuće UI bugove.
4) Izradite ekran za todose#
Kreirajte lib/todos/todos_screen.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../data/todo_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
class TodosScreen extends StatefulWidget {
const TodosScreen({super.key});
@override
State<TodosScreen> createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
final _service = TodoService();
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Todos'),
actions: [
IconButton(
onPressed: () => FirebaseAuth.instance.signOut(),
icon: const Icon(Icons.logout),
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(hintText: 'Add a todo...'),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () async {
final text = _controller.text.trim();
if (text.isEmpty) return;
_controller.clear();
await _service.addTodo(text);
},
child: const Text('Add'),
),
],
),
),
Expanded(
child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: _service.watchTodos(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final docs = snapshot.data!.docs;
if (docs.isEmpty) return const Center(child: Text('No todos yet.'));
return ListView.builder(
itemCount: docs.length,
itemBuilder: (context, i) {
final d = docs[i];
final data = d.data();
final title = (data['title'] ?? '') as String;
final done = (data['done'] ?? false) as bool;
return Dismissible(
key: ValueKey(d.id),
background: Container(color: Colors.red),
onDismissed: (_) => _service.deleteTodo(d.id),
child: CheckboxListTile(
value: done,
title: Text(title),
onChanged: (v) => _service.toggleDone(d.id, v ?? false),
),
);
},
);
},
),
),
],
),
);
}
}Zatim ažurirajte auth gate tako da prikazuje TodosScreen kada je korisnik prijavljen.
# Korak 5: Firestore indeksi + osnove performansi#
Problemi s Firestore performansama najčešće dolaze od:
- neograničenih upita (bez limita),
- nedostajućih kompozitnih indeksa,
- “chatty” UI-ja (previše listenera),
- prevelikih dokumenata.
Dodajte limite upitima#
Ako lista može rasti, dodajte .limit(50) i kasnije uvedite paginaciju.
return _todos
.orderBy('createdAt', descending: true)
.limit(50)
.snapshots();Znajte kada vam treba indeks#
Ako radite upit s više where uvjeta + orderBy, Firestore će često zahtijevati kompozitni indeks. U produkciji to postaje runtime error ako niste kreirali indeks.
| Pattern upita | Obično treba kompozitni indeks? | Primjer |
|---|---|---|
orderBy(createdAt) | Ne | Najnovije stavke |
where(done == false) | Ne | Samo filtriranje |
where(done == false).orderBy(createdAt) | Često da | Filtriranje + sortiranje |
where(status in [...]).orderBy(priority) | Često da | Složene liste |
💡 Savjet: Kada Firestore izbaci index error, uključit će direktan link u konzoli za kreiranje potrebnog indeksa. Kliknite ga, kreirajte indeks i “zaključajte” shape upita u codebaseu kako se ne bi neočekivano mijenjao.
# Korak 6: Cloud Functions (serverska logika kojoj možete vjerovati)#
Firestore rules kontroliraju pristup, ali nisu potpuno programsko okruženje. Cloud Functions je mjesto gdje stavljate privilegiranu logiku poput:
- dodjele uloga,
- pisanja audit logova,
- provođenja ograničenja preko više dokumenata,
- pozivanja vanjskih API-ja.
1) Inicijalizirajte Functions#
U rootu projekta:
firebase init functionsOdaberite:
- Jezik: JavaScript ili TypeScript (TypeScript preporučen)
- Koristiti ESLint: opcionalno
- Instalirati dependencies: da
Ako koristite TypeScript, deploy radite iz functions/.
2) Primjer funkcije: kreiranje korisničkog profila pri registraciji#
Kreirajte auth trigger koji zapisuje users/{uid} kada se korisnik registrira.
functions/src/index.ts (TypeScript):
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
export const onUserCreate = functions.auth.user().onCreate(async (user) => {
const uid = user.uid;
const email = user.email ?? null;
await admin.firestore().collection("users").doc(uid).set({
email,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
}, { merge: true });
});Deploy:
firebase deploy --only functions3) Primjer callable funkcije: postavljanje “admin” claima (ograničeno)#
Ne dopustite klijentima da dodjeljuju uloge. Ovu funkciju smiju koristiti samo postojeći admini.
export const setAdminClaim = functions.https.onCall(async (data, context) => {
const callerUid = context.auth?.uid;
if (!callerUid) throw new functions.https.HttpsError("unauthenticated", "Sign in required.");
const caller = await admin.auth().getUser(callerUid);
const isAdmin = caller.customClaims?.admin === true;
if (!isAdmin) throw new functions.https.HttpsError("permission-denied", "Admin only.");
const targetUid = data.uid as string;
await admin.auth().setCustomUserClaims(targetUid, { admin: true });
return { ok: true };
});Na Flutter strani, pozvali biste je s paketom cloud_functions kada je potrebno (npr. interna admin aplikacija).
⚠️ Upozorenje: Callable functions nisu automatski “sigurne”. Uvijek validirajte
context.authi autorizirajte pozivatelja. Mnogi Firebase incidenti nastaju zbog funkcija koje pretpostavljaju “UI aplikacije sprječava zloupotrebu”.
# Korak 7: Ojačavanje aplikacije (Rules, validacija, error handling)#
1) Dodajte osnovnu validaciju u Firestore rules#
Spriječite prazne naslove i preduge stringove (trošak i UX).
match /users/{userId}/todos/{todoId} {
allow create: if isSignedIn()
&& request.auth.uid == userId
&& request.resource.data.title is string
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 140;
allow update, delete, read: if isSignedIn() && request.auth.uid == userId;
}2) Izbjegavajte spremanje tajni u aplikaciju#
Sve što je u klijentu može se izvući. Koristite Functions za:
- API ključeve koji moraju ostati tajni,
- billing operacije,
- provjere dozvola između korisnika.
3) Centralizirajte mapiranje Firebase grešaka#
FirebaseAuth greške nisu user-friendly. Mapirajte ih.
String authMessage(Object e) {
final s = e.toString();
if (s.contains('wrong-password')) return 'Wrong password.';
if (s.contains('user-not-found')) return 'No user found for that email.';
if (s.contains('email-already-in-use')) return 'Email already in use.';
return 'Something went wrong. Please try again.';
}# Korak 8: Deploy (Functions + produkcijski checklist)#
Flutter aplikacije isporučujete kroz App Store / Play Store, ali i vaš Firebase backend mora biti “deployan” i zaključan.
1) Deploy Cloud Functions#
Već ste koristili:
firebase deploy --only functions2) Potvrdite da rules nisu u “test mode”#
U Firestore rules izbjegavajte:
allow read, write: if true;
3) Provjerite zahtjeve billing plana#
Neke Firebase značajke (i razine korištenja) zahtijevaju Blaze plan. Nemojte to otkriti u tjednu lansiranja.
| Značajka | Tipično zahtijeva Blaze? | Zašto je važno |
|---|---|---|
| Firestore osnovno | Ne uvijek | Postoji free tier, ali limiti vrijede |
| Cloud Functions (neki triggeri/egress) | Često | Vanjski mrežni pozivi i skaliranje |
| Authentication | Uglavnom ne | Ali SMS/phone ima ograničenja |
| Scheduled Functions | Često | Uobičajeno za batch poslove |
4) Dodajte osnovni monitoring#
- Firebase Console → Functions logs
- Razmislite o izvozu logova u BigQuery za veće aplikacije
- Postavite alerting na skokove grešaka
ℹ️ Napomena: Googleovo DORA istraživanje dosljedno pokazuje da timovi s jakom observability praksom i brzim feedback loopovima isporučuju češće uz nižu stopu promjena koje završavaju greškom. U praksi: logovi i alertovi smanjuju “tihe kvarove” koji znaju pojesti tjedne.
# Česte zamke (i kako ih izbjeći)#
- 1Ostavljanje Firestorea u test modeu — Zaključajte rules rano i gradite UI oko permission errora.
- 2Ravne kolekcije s userId poljima — Može funkcionirati, ali je lakše pogriješiti. Preferirajte
users/{uid}/...za podatke po korisniku. - 3Oslanjanje na klijent za sigurnost — Sve što klijent može napraviti, može i napadač. Koristite rules + functions.
- 4Bez strategije indeksa — Ako dodajete ekrane s “filterima + sortiranjem”, planirajte i dokumentirajte query oblike da vas indeksi ne iznenade.
- 5Preveliki dokumenti — Držite dokumente malima; velike blobove spremite u Cloud Storage i referencirajte URL-ove u Firestoreu.
Za timove kojima treba produkcijska pomoć (arhitektura, pregled rulesa, CI/CD, kontrola troškova), pogledajte Samioda mobile & web development. Ako još birate stack, pročitajte Flutter vs React Native (2026).
# Ključne poruke#
- Koristite FlutterFire CLI za ispravnu konfiguraciju Firebasea kroz platforme i izbjegavanje krhke ručne postave.
- Prvo izgradite auth, zatim dizajnirajte Firestore oko security rulesa (npr.
users/{uid}/...) kako bi kontrola pristupa ostala jednostavna. - Preferirajte
serverTimestamp()zacreatedAt/updatedAtkako biste spriječili bugove u sortiranju i manipulaciju. - Koristite Cloud Functions za privilegiranu logiku (uloge, integracije, provjere integriteta) i uvijek validirajte
context.auth. - Rano dodajte limite, indekse i logiranje; većina Firebase “scale” problema su problemi upita + rulesa + observabilityja.
# Zaključak#
Sada imate funkcionalnu Flutter + Firebase aplikaciju s Authentication, Firestore CRUD i Cloud Functions — dovoljno da isporučite MVP i sigurno ga razvijate dalje. Sljedeći korak je ojačati rules, dokumentirati query patterne, dodati monitoring te planirati paginaciju i uloge kako proizvod raste.
Ako želite da Samioda ubrza isporuku ili pregleda vašu Firebase sigurnost i arhitekturu prije lansiranja, kontaktirajte nas putem mobile & web development.
FAQ
Više iz kategorije Mobilni razvoj
Sve →Koliko košta MVP mobilne aplikacije? Realistična razrada (2026)
Objašnjenje troška MVP-a mobilne aplikacije uz realne razrade po funkcionalnostima, raspona prema tipu aplikacije i usporedbu Fluttera i nativnog razvoja kako biste realno isplanirali budžet.
Koliko košta razvoj Flutter aplikacije u 2026.? (Realni budžeti prema složenosti aplikacije)
Saznajte realan trošak razvoja Flutter aplikacije u 2026. kroz budžete po složenosti, detaljnu tablicu troškova i usporedbu native vs Flutter za iOS i Android.
Upravljanje stanjem u Flutteru 2026: Riverpod vs Bloc vs Provider
Praktična usporedba Riverpoda, Bloca i Providera za flutter state management 2026—performanse, DX, testiranje, arhitektura i kada odabrati koji pristup.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Upravljanje stanjem u Flutteru 2026: Riverpod vs Bloc vs Provider
Praktična usporedba Riverpoda, Bloca i Providera za flutter state management 2026—performanse, DX, testiranje, arhitektura i kada odabrati koji pristup.
Koliko košta razvoj Flutter aplikacije u 2026.? (Realni budžeti prema složenosti aplikacije)
Saznajte realan trošak razvoja Flutter aplikacije u 2026. kroz budžete po složenosti, detaljnu tablicu troškova i usporedbu native vs Flutter za iOS i Android.
Koliko košta MVP mobilne aplikacije? Realistična razrada (2026)
Objašnjenje troška MVP-a mobilne aplikacije uz realne razrade po funkcionalnostima, raspona prema tipu aplikacije i usporedbu Fluttera i nativnog razvoja kako biste realno isplanirali budžet.