Flutter Supabase Login Page: A Beginner's Guide

by Alex Braham 48 views

Hey guys! Ever wanted to build a secure and user-friendly login page for your Flutter app? Well, you're in luck! This guide will walk you through creating a Flutter Supabase login page. We'll use Supabase, a fantastic open-source Firebase alternative, to handle user authentication and data storage. By the end, you'll have a fully functional login page that's ready to go. Let's dive in!

Why Flutter and Supabase?

So, why choose Flutter and Supabase for your login page project? Well, let's break it down. Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. This means you can write your code once and deploy it on multiple platforms, saving you tons of time and effort. Plus, Flutter's hot reload feature lets you see your changes instantly, which is super helpful for rapid development. On the other hand, Supabase is an open-source Firebase alternative that provides a backend-as-a-service (BaaS). It offers a range of features, including user authentication, real-time database, and storage. Supabase is built on top of PostgreSQL, a powerful and reliable database, and it's easy to use, especially if you're already familiar with SQL. The combination of Flutter and Supabase is a match made in heaven because they make it easy to develop scalable and maintainable applications. The two technologies work perfectly together to get you up and running without dealing with the complex backend configuration. Both technologies are open source and have a lot of community support, so you will always find help when you need it.

Flutter's declarative UI approach makes it easy to build complex layouts, and its widgets are highly customizable. Supabase's user authentication features are secure and easy to integrate, with support for various authentication methods like email/password, social logins, and more. This combination lets you focus on building your app's frontend while leaving the backend complexities to Supabase. Using Supabase saves you time and resources by handling all the backend functionalities, so you don’t need to manage servers, databases, or authentication systems. You can focus on the user experience and create an amazing application, all while saving time and money. It also provides a great developer experience, with a straightforward API and excellent documentation, making it easy to get started and scale your applications as they grow.

Benefits of Flutter

  • Cross-platform development: Build for iOS, Android, web, and desktop from a single codebase.
  • Fast development: Hot reload and a rich set of widgets make development efficient.
  • Beautiful UI: Flutter's widgets allow for creating stunning and customized user interfaces.

Benefits of Supabase

  • Open-source: Freedom and flexibility with an open-source backend.
  • Easy to use: Simple integration and a user-friendly interface.
  • Scalable: Built on PostgreSQL, ensuring scalability and reliability.

Setting Up Your Supabase Project

Alright, let's get our hands dirty and set up your Supabase project. First things first, you'll need a Supabase account. Head over to the Supabase website and create an account if you don't already have one. Once you're logged in, create a new project. You'll be prompted to enter a project name, choose a region, and select a database password. Make sure to keep this password safe, as you'll need it later. After your project is created, navigate to the Authentication section in your Supabase dashboard. Here, you can enable email/password authentication (which we'll use in this tutorial) and configure other authentication providers if you wish. Under the settings for authentication, you can also modify the templates for email verification and password reset emails to match your branding. This is crucial for user experience. Also, Supabase has amazing documentation so you will be able to set everything up as you wish.

Next up, grab your project's API keys. You'll find these in the Settings > API section of your Supabase dashboard. You'll need the anon key (public key) and the service_role key (private key) for different purposes. The anon key is used for client-side operations (like in your Flutter app), while the service_role key is used for server-side operations (which we won't get into in this tutorial). It's super important to keep your service_role key secret! Now that your Supabase project is all set, let's move on to the Flutter side.

Step-by-Step Setup

  1. Create a Supabase Account: Visit the Supabase website and sign up.
  2. Create a New Project: In your Supabase dashboard, create a new project and set the database password.
  3. Enable Authentication: Go to the Authentication section and enable email/password authentication.
  4. Get API Keys: Find the anon key in the API settings. Keep the service_role key private.

Flutter Project Setup

Now, let's create a new Flutter project. Open your terminal or command prompt and run the following command: flutter create flutter_supabase_login. Replace flutter_supabase_login with your desired project name. Once the project is created, navigate into the project directory using cd flutter_supabase_login. Next, you'll need to add the Supabase Flutter package to your project. Open your pubspec.yaml file and add the following line under the dependencies section:

supabase_flutter: ^2.0.0

Make sure to use the latest version of the package. Save the pubspec.yaml file, and Flutter will automatically fetch the package and its dependencies. If it doesn't, run flutter pub get in your terminal. This command tells Flutter to resolve all of your dependencies. It’s like loading all the ingredients before you start cooking! Now, let's initialize Supabase in your main.dart file. Import the supabase_flutter package and initialize Supabase with your project's URL and anon key. Here's how it should look:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

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

  await Supabase.initialize(
    url: 'YOUR_SUPABASE_URL',
    anonKey: 'YOUR_SUPABASE_ANON_KEY',
  );

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Supabase Login',
      home: LoginPage(),
    );
  }
}

Make sure to replace 'YOUR_SUPABASE_URL' and 'YOUR_SUPABASE_ANON_KEY' with your actual Supabase project URL and anon key. You can find these values in the API settings of your Supabase dashboard. With all this configuration, the ground is set to design and build our first login page.

Project Setup Checklist

  1. Create a Flutter Project: Use flutter create your_project_name.
  2. Add Dependencies: Add supabase_flutter to pubspec.yaml.
  3. Initialize Supabase: Initialize Supabase in main.dart with your project URL and anon key.

Building the Login Page UI

Time to get our hands dirty and build the UI for the login page! In this section, we'll create a basic login form with email and password fields, and a button to submit the login request. Create a new file called login_page.dart in your lib directory. Inside this file, we'll create a StatelessWidget or StatefulWidget (depending on your preference) for the login page. In the build method, you'll return a Scaffold widget, which provides a basic layout for your page. Inside the Scaffold, you can add an AppBar for the title and a Center widget to center the content. Inside the Center widget, you'll place a Column widget to arrange the form elements vertically. The Column will contain TextFormField widgets for email and password input and a ElevatedButton for the login action. To style the form, you can use various properties of the TextFormField and ElevatedButton, such as decoration, hintText, style, and more. Using these properties, you can create a beautiful form.

Here’s a basic structure to get you started:

import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Login'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
              ),
              SizedBox(height: 20),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'Password',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              SizedBox(height: 20),
              ElevatedButton(
                child: Text('Login'),
                onPressed: () {
                  // Implement login functionality here
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

This is just a starting point. Feel free to customize the UI with colors, fonts, and layouts that match your app's design. Use padding to give the form elements some breathing room, and consider using a Container widget to add a background or other visual effects. The layout is just the first step. You can make it look even better with icons and styles.

UI Components Summary

  • Scaffold: Provides the basic layout structure.
  • AppBar: Displays the title of the page.
  • Center: Centers the content.
  • Column: Arranges the form elements vertically.
  • TextFormField: Input fields for email and password.
  • ElevatedButton: The login button.

Implementing Login Functionality

Now, let’s make our login page actually do something. We'll implement the login functionality using the Supabase signInWithPassword method. In the LoginPage widget, add an async function called _login (or whatever name you like). Inside this function, get the email and password from the text controllers. Then, call Supabase.instance.client.auth.signInWithPassword(email: email, password: password) and await the result. This will attempt to sign in the user with the provided credentials. The signInWithPassword method returns a AuthResponse object. Check the user property to see if the login was successful. If the user is not null, the login was successful, and you can navigate the user to the next screen (e.g., the home page). If there was an error, you can display an error message to the user. For this, you could show a SnackBar or display the error messages from the response.

Here’s how to implement it:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  bool _isLoading = false;

  Future<void> _login() async {
    setState(() {
      _isLoading = true;
    });
    final email = _emailController.text.trim();
    final password = _passwordController.text.trim();

    try {
      final AuthResponse res = await Supabase.instance.client.auth.signInWithPassword(
        email: email,
        password: password,
      );

      final Session? session = res.session;
      final User? user = res.user;

      if (user != null && session != null) {
        // Login successful
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Login successful!'),
          ),
        );
        // Navigate to the next screen (e.g., home page)
      } else {
        // Login failed
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Login failed. Please check your credentials.'),
          ),
        );
      }
    } on AuthException catch (error) {
      // Handle authentication errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(error.message),
        ),
      );
    } catch (error) {
      // Handle other errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('An unexpected error occurred.'),
        ),
      );
    }
    finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Login'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
              ),
              SizedBox(height: 20),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'Password',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              SizedBox(height: 20),
              ElevatedButton(
                child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text('Login'),
                onPressed: _isLoading ? null : () {
                  _login();
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

This code includes error handling with try-catch blocks to catch authentication errors and other potential issues. When you implement the navigation to the home page, you'll replace the comment with the appropriate navigation code, using Navigator.push or Navigator.pushReplacement for example. Also, it’s good practice to add a loading indicator when the user clicks the login button to show that the app is processing the request.

Login Functionality Breakdown

  1. Get Input: Retrieve email and password from text controllers.
  2. Call signInWithPassword: Use Supabase.instance.client.auth.signInWithPassword().
  3. Handle Response: Check for success and handle errors.
  4. Navigate: Navigate to the next screen on successful login.
  5. Error Handling: Show error messages to the user.

Adding a Logout Functionality

Let’s add a logout feature to complete the login cycle. To add this, you'll need to create a button or a menu option that calls the Supabase.instance.client.auth.signOut() method. This method clears the user's session and signs them out. You might want to put the logout option on the home screen or in a settings menu. Here's how it would look in the home screen, to have a button:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  Future<void> _signOut() async {
    await Supabase.instance.client.auth.signOut();
    // Navigate back to the login page or initial screen
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Welcome!'),
            ElevatedButton(
              onPressed: _signOut,
              child: Text('Logout'),
            ),
          ],
        ),
      ),
    );
  }
}

When the user taps the logout button, the _signOut function is executed. This function calls signOut() and then navigates the user back to the login page (or any screen you like). Make sure to handle the navigation appropriately (e.g., using Navigator.pushReplacement to replace the current screen with the login page). The logout implementation is extremely simple, and it provides a clean way to ensure the user’s session is terminated when they want to log out.

Logout Implementation

  1. Call signOut(): Use Supabase.instance.client.auth.signOut().
  2. Navigate: Navigate the user back to the login page.

Conclusion and Next Steps

And there you have it! You've successfully built a Flutter Supabase login page that handles user authentication. You've learned how to set up your Supabase project, integrate Supabase into your Flutter app, create the UI, implement login functionality, and add logout functionality. This is just the beginning. There’s a whole universe of possibilities for your app once you set up authentication. Now, you can add more features. You can integrate other authentication providers offered by Supabase, implement user registration, add password reset functionality, and explore real-time data features. You can also customize the UI to match your brand and create a more polished user experience. Keep exploring and experimenting, and don't be afraid to try new things. The journey of app development is always an exciting and learning adventure.

Next Steps

  • Add Registration: Implement user registration functionality.
  • Implement Password Reset: Enable password reset features.
  • Explore Real-time Data: Use Supabase's real-time features to build dynamic apps.
  • Customize UI: Enhance the UI to match your app's design.

That’s it, guys! Building a login page with Flutter and Supabase is a fantastic project that can set the stage for many more awesome projects. Happy coding!