DEV Community

Samuel Adekunle
Samuel Adekunle

Posted on • Originally published at techwithsam.dev

Flutter Firebase Tutorial 2026: Complete Auth + Firestore Integration (Simple Notes App) 🔥

Hey everyone! Today, we're tackling the most requested Firebase integration: Auth and Firestore are still dominating for quick, scalable backends. We'll build a Notes app with email/password login and real-time Firestore sync while fixing common setup headaches along the way. 

Set up and Why?

Why Firebase now? Real-time data, offline persistence, and auth in minutes - plus emerging AI tools like Firebase Genkit.

Setup:

  1. Go to console.firebase.google.com → Create a New Firebase project.
  2. Click on add app → Select Flutter → Follow through the process
    Add Firebase to your Flutter App

  3. Enable Auth methods (Email & Password) and Firestore in the console.
    Firebase Authentication

Start with a new project or your counter app: flutter create firebase_notes.

pubspec.yaml:

  firebase_core: ^4.3.0
  firebase_auth: ^6.1.3
  cloud_firestore: ^6.1.1
Enter fullscreen mode Exit fullscreen mode

flutter pub get.

Initialize Firebase (main.dart):

import 'package:flutter/material.dart';
import 'package:flutter_firebase_tutorial/firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  runApp(const MyApp());
}
Enter fullscreen mode Exit fullscreen mode

Generate firebase_options.dart via FlutterFire CLI: dart pub global pub global activate flutterfire_cli then flutterfire configure.

Auth screen (login_signup.dart):

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_firebase_tutorial/home.dart';

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

  @override
  State<LoginSignUp> createState() => _LoginSignUpState();
}

class _LoginSignUpState extends State<LoginSignUp> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login SignUp Screen')),
      body: Column(
        children: [
          Text('Login SignUp Screen'),

          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
          ),
          SizedBox(height: 16.0),
          TextFormField(controller: _passwordController, obscureText: true),
          SizedBox(height: 16.0),
          ElevatedButton(
            child: Text("Login"),
            onPressed: () async {
              String email = _emailController.text.trim();
              String password = _passwordController.text.trim();

              try {
                // Try to sign in
                await FirebaseAuth.instance
                    .signInWithEmailAndPassword(
                      email: email,
                      password: password,
                    )
                    .then((c) {
                      if (!context.mounted) return;
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (_) => HomeScreen()),
                      );
                    });
              } on FirebaseAuthException catch (e) {
                if (e.code == 'user-not-found') {
                  // If user not found, create a new account
                  await FirebaseAuth.instance.createUserWithEmailAndPassword(
                    email: email,
                    password: password,
                  );
                } else {
                  // Handle other errors
                  print('Error: $e');
                }
              }
            },
          ),
          SizedBox(height: 16.0),
          ElevatedButton(
            child: Text("Sign Up"),
            onPressed: () async {
              String email = _emailController.text.trim();
              String password = _passwordController.text.trim();

              try {
                // Try to sign in
                await FirebaseAuth.instance
                    .createUserWithEmailAndPassword(
                      email: email,
                      password: password,
                    )
                    .then((v) {
                      if (!context.mounted) return;
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (_) => HomeScreen()),
                      );
                    });
              } on FirebaseAuthException catch (e) {
                if (e.code == 'user-not-found') {
                  // If user not found, create a new account
                  await FirebaseAuth.instance.createUserWithEmailAndPassword(
                    email: email,
                    password: password,
                  );
                } else {
                  // Handle other errors
                  print('Error: $e');
                }
              }
            },
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Firestore: Collection 'notes' per user (users/{uid}/notes). 

Stream notes:

FirebaseFirestore.instance
  .collection('users')
  .doc(uid)
  .collection('notes')
  .snapshots()
Enter fullscreen mode Exit fullscreen mode

Add notes:

await FirebaseFirestore.instance
  .collection('users')
  .doc(uid)
  .collection('notes')
  .add({'title': title, 'content': content, 'timestamp': FieldValue.serverTimestamp()});
Enter fullscreen mode Exit fullscreen mode

Full Code (update the field where necessary). UI: ListView.builder with StreamBuilder.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  User? user = FirebaseAuth.instance.currentUser;
  final db = FirebaseFirestore.instance;
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Screen')),
      body: Column(
        children: [
          Text('Home Screen'),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: db
                  .collection('texts')
                  .doc(user!.uid)
                  .collection('user_texts')
                  .snapshots(),
              builder:
                  (
                    BuildContext context,
                    AsyncSnapshot<QuerySnapshot> snapshot,
                  ) {
                    if (snapshot.hasError) {
                      return Text('Error: ${snapshot.error}');
                    }
                    if (snapshot.connectionState == ConnectionState.waiting) {
                      return Center(child: CircularProgressIndicator());
                    }
                    final data = snapshot.requireData;
                    return ListView.builder(
                      itemCount: data.size,
                      itemBuilder: (context, index) {
                        return ListTile(title: Text(data.docs[index]['text']));
                      },
                    );
                  },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showModalBottomSheet(
            context: context,
            builder: (context) {
              return Container(
                color: Colors.white,
                child: Column(
                  children: [
                    SizedBox(height: 16.0),
                    TextFormField(
                      maxLines: 4,
                      controller: _textController,
                      decoration: InputDecoration(labelText: 'Enter some text'),
                    ),
                    SizedBox(height: 16.0),
                    ElevatedButton(
                      child: Text("Submit"),
                      onPressed: () {
                        String enteredText = _textController.text;
                        print('Entered Text: $enteredText');
                        db
                            .collection('texts')
                            .doc(user!.uid)
                            .collection('user_texts')
                            .add({'text': enteredText});
                        _textController.clear();
                        Navigator.pop(context);
                      },
                    ),
                  ],
                ),
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Basic Firestore Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/{document=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That's all! Test the app and see how it works.

I hope you've learn something incredible. Press that follow button if you're not following me yet. Also, make sure to subscribe to the newsletter so you're notified when I publish a new article.

🔗 Let's Connect 🔗 → Github | Twitter | LinkedIn.

Join my Community 👨‍💻👨‍💻 on Discord.

Subscribe to my YouTube channel.

Happy Building! 🥰👨‍💻

Top comments (0)