gmail_client_flutter 1.0.1 copy "gmail_client_flutter: ^1.0.1" to clipboard
gmail_client_flutter: ^1.0.1 copied to clipboard

Flutter bindings for gmail_client — Riverpod providers, Google Sign-In, and platform-specific OAuth utils for Gmail integration.

gmail_client_flutter #

Flutter bindings for gmail_client. Provides Riverpod providers, Google Sign-In integration, and platform-specific OAuth utilities.

Features #

  • Riverpod providers for auth state, inbox, compose, and email detail
  • GoogleAuthService — Google Sign-In and Gmail OAuth token exchange (web + mobile)
  • Platform-specific OAuth — web popup flow via gmail_oauth
  • URL utils — clean OAuth params from browser address bar
  • Attachment management — file picking and MIME type mapping

Prerequisites #

  • gmail_client added as dependency
  • ❌ Supabase project initialized with supabase_flutter
  • ❌ Database tables created (see gmail_client docs)
  • ❌ Edge Functions deployed (google-auth-callback, send-email, list-emails, get-email)
  • ❌ Google Cloud OAuth credentials configured
  • .env registered as asset in pubspec.yaml (if using flutter_dotenv)

Getting started #

flutter pub add gmail_client_flutter

1. Register .env as an asset (if using flutter_dotenv) #

# pubspec.yaml
flutter:
  uses-material-design: true
  assets:
    - .env

2. Initialize in main.dart #

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:gmail_client_flutter/gmail_client_flutter.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

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

  await dotenv.load();

  await Supabase.initialize(
    url: dotenv.env['SUPABASE_URL']!,
    anonKey: dotenv.env['SUPABASE_ANON_KEY']!,
  );

  runApp(const ProviderScope(child: MyApp()));
}

3. Use providers in your widgets #

class InboxPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final inboxState = ref.watch(inboxProvider);

    if (inboxState.isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    return ListView.builder(
      itemCount: inboxState.emails.length,
      itemBuilder: (context, index) {
        final email = inboxState.emails[index];
        return ListTile(
          title: Text(email.subject ?? ''),
          subtitle: Text(email.from ?? ''),
        );
      },
    );
  }
}

Security #

Do not commit client_secret or .env to your repository. The .env file is listed in .gitignore. Only .env.example should be committed with placeholder values.

The Google OAuth client secret is stored server-side in org_email_config and never exposed to the Flutter client. Use .env only for non-sensitive values like the Supabase URL and anon key.

Providers #

Provider Type Description
emailServiceProvider Provider<EmailService> Core email service instance
supabaseClientProvider Provider<SupabaseClient> Supabase client singleton
emailConnectionProvider FutureProvider<bool> Whether Gmail is connected
connectedEmailProvider FutureProvider<String?> Connected email address
displayNameProvider FutureProvider<String?> Sender display name
authStateProvider StreamProvider<AuthState> Supabase auth state stream
currentUserProvider Provider<User?> Current authenticated user
isLoggedInProvider Provider<bool> Whether session is active
inboxProvider StateNotifierProvider Inbox state (emails, loading, pagination)
composeProvider StateNotifierProvider Compose state (attachments, sending)
emailDetailProvider FutureProvider.family Email detail by message ID
googleAuthServiceProvider Provider<GoogleAuthService> Google Sign-In service

Web vs Mobile OAuth #

The OAuth flow differs by platform. Use the appropriate method:

Web (popup) #

final googleAuth = ref.read(googleAuthServiceProvider);

// Opens a popup window for Gmail OAuth consent
final code = await googleAuth.getGmailAuthCodeForWeb('YOUR_WEB_CLIENT_ID');

if (code != null) {
  await googleAuth.connectGmailAccount(
    serverAuthCode: code,
    email: 'user@gmail.com',
    userId: supabaseUser.id,
  );
}

Mobile (iOS/Android — native Google Sign-In) #

final googleAuth = ref.read(googleAuthServiceProvider);

await googleAuth.initialize(
  webClientId: 'xxx.apps.googleusercontent.com',
  iosClientId: 'xxx.apps.googleusercontent.com',
);

final account = await googleAuth.signIn();
final code = await googleAuth.getServerAuthCode(account!);

if (code != null) {
  await googleAuth.connectGmailAccount(
    serverAuthCode: code,
    email: account.email,
    userId: supabaseUser.id,
  );
}

GoogleAuthService #

final googleAuth = ref.read(googleAuthServiceProvider);

// Initialize with OAuth client IDs
await googleAuth.initialize(
  webClientId: 'xxx.apps.googleusercontent.com',
  iosClientId: 'xxx.apps.googleusercontent.com',
);

// Sign in (mobile)
final account = await googleAuth.signIn();

// Get OAuth code (mobile)
final code = await googleAuth.getServerAuthCode(account!);

// Get OAuth code via web popup
final code = await googleAuth.getGmailAuthCodeForWeb('web-client-id');

// Exchange code for Gmail tokens
await googleAuth.connectGmailAccount(
  serverAuthCode: code!,
  email: 'user@gmail.com',
  userId: supabaseUser.id,
);

Complete Flow Example #

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:gmail_client_flutter/gmail_client_flutter.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

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

  // 1. Load environment variables
  await dotenv.load();

  // 2. Initialize Supabase
  await Supabase.initialize(
    url: dotenv.env['SUPABASE_URL']!,
    anonKey: dotenv.env['SUPABASE_ANON_KEY']!,
  );

  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Consumer(
        builder: (context, ref, _) {
          final isLoggedIn = ref.watch(isLoggedInProvider);
          return isLoggedIn ? const HomePage() : const LoginPage();
        },
      ),
    );
  }
}

class LoginPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // Sign in with Google (Supabase Auth)
            await Supabase.instance.client.auth.signInWithOAuth(
              OAuthProvider.google,
            );
          },
          child: const Text('Sign in with Google'),
        ),
      ),
    );
  }
}

class HomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final isConnected = ref.watch(emailConnectionProvider);
    final inbox = ref.watch(inboxProvider);
    final connectedEmail = ref.watch(connectedEmailProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text(connectedEmail.value ?? 'Gmail App'),
      ),
      body: isConnected.when(
        data: (connected) => connected
            ? ListView.builder(
                itemCount: inbox.emails.length,
                itemBuilder: (_, i) => ListTile(
                  title: Text(inbox.emails[i].subject ?? ''),
                  subtitle: Text(inbox.emails[i].from ?? ''),
                ),
              )
            : const Center(child: Text('Connect your Gmail account')),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (e, _) => Center(child: Text('Error: $e')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Navigate to compose screen
          Navigator.push(context,
              MaterialPageRoute(builder: (_) => const ComposeScreen()));
        },
        child: const Icon(Icons.edit),
      ),
    );
  }
}

Troubleshooting #

Problem Cause Solution
FileNotFoundError when loading .env .env not registered as asset in pubspec.yaml Add assets: - .env under flutter: in pubspec.yaml and restart
AssertionError on SUPABASE_URL / SUPABASE_ANON_KEY .env variables are missing or misspelled Check .env has SUPABASE_URL= and SUPABASE_ANON_KEY=
CORS errors when calling Edge Functions from Chrome localhost and supabase.co are different origins Run with: flutter run -d chrome --web-port=3000 --web-browser-flag=--disable-web-security
Google Sign-In not working on web Missing or wrong webClientId Ensure GOOGLE_WEB_CLIENT_ID in .env matches Google Cloud Console
inboxProvider always returns empty list Gmail not connected, or no session Check isLoggedInProvider and emailConnectionProvider first
GmailAuthException: User not authenticated Supabase session expired or missing Sign in with Supabase.instance.client.auth.signInWithOAuth()
GmailClientException: relation does not exist Database tables not created Run supabase db push or create tables manually (see gmail_client docs)
Provider returns null or loading forever ProviderScope not wrapping runApp Ensure runApp(const ProviderScope(child: MyApp()))
0
likes
130
points
94
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter bindings for gmail_client — Riverpod providers, Google Sign-In, and platform-specific OAuth utils for Gmail integration.

Repository (GitHub)
View/report issues

Topics

#gmail #email #supabase #riverpod #google-sign-in

License

MIT (license)

Dependencies

extension_google_sign_in_as_googleapis_auth, file_picker, flutter, flutter_riverpod, gmail_client, google_sign_in, supabase_flutter

More

Packages that depend on gmail_client_flutter