Using Google Cloud Platform (GCP) Services in Flutter Apps

Flutter has revolutionized cross-platform mobile app development by enabling developers to write code once and deploy it across multiple platforms. Combining Flutter’s capabilities with Google Cloud Platform (GCP) services offers a powerful synergy for building scalable, robust, and feature-rich mobile applications. This blog post explores how to integrate various GCP services into your Flutter apps.

Why Integrate GCP Services into Flutter Apps?

  • Scalability: Leverage GCP’s infrastructure for handling growing user bases.
  • Data Storage: Use Cloud Storage for storing media files, user data, etc.
  • Authentication: Integrate Firebase Authentication (a GCP service) for user management.
  • Backend Logic: Employ Cloud Functions for running server-side code without managing servers.
  • Machine Learning: Utilize GCP’s AI and ML services through APIs.

Prerequisites

  • A Google Cloud Platform account with a project set up.
  • Flutter development environment configured.
  • Basic understanding of Flutter and Dart.

Step 1: Setting Up Firebase (Authentication and Firestore)

Firebase, being a part of GCP, is frequently used with Flutter apps. It provides services like authentication and real-time databases.

Creating a Firebase Project

  1. Go to the Firebase Console.
  2. Click “Add project.”
  3. Follow the prompts to name and create your project.

Add Firebase to Your Flutter App

  1. Install the Firebase CLI:
    npm install -g firebase-tools
  2. Login to Firebase:
    firebase login
  3. In your Flutter project root directory, run:
    flutterfire configure
  4. Follow the on-screen instructions to link your Firebase project to your Flutter app.
  5. Add necessary Firebase packages to your pubspec.yaml:
    dependencies:
      firebase_core: ^2.15.0
      firebase_auth: ^4.6.0
      cloud_firestore: ^4.9.0
    
  6. Run flutter pub get to install the packages.

Initializing Firebase

Initialize Firebase in your Flutter app, typically in the main.dart file:

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

import 'firebase_options.dart'; // Ensure this file is correctly generated

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter with GCP',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('GCP Flutter Example'),
        ),
        body: Center(
          child: Text('Firebase initialized successfully!'),
        ),
      ),
    );
  }
}

Step 2: Implementing Firebase Authentication

User authentication is critical for many apps. Firebase Authentication simplifies this process.

User Registration

Implement user registration using Firebase Authentication:

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

class SignUpPage extends StatefulWidget {
  @override
  _SignUpPageState createState() => _SignUpPageState();
}

class _SignUpPageState extends State {
  final _auth = FirebaseAuth.instance;
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sign Up')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password'),
            ),
            ElevatedButton(
              onPressed: () async {
                try {
                  final newUser = await _auth.createUserWithEmailAndPassword(
                      email: _emailController.text,
                      password: _passwordController.text);
                  if (newUser != null) {
                    Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomePage()));
                  }
                } catch (e) {
                  print('Failed to create user: $e');
                  // Handle errors here
                }
              },
              child: const Text('Sign Up'),
            ),
          ],
        ),
      ),
    );
  }
}

User Login

Implement user login using Firebase Authentication:

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

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

class _LoginPageState extends State {
  final _auth = FirebaseAuth.instance;
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password'),
            ),
            ElevatedButton(
              onPressed: () async {
                try {
                  await _auth.signInWithEmailAndPassword(
                      email: _emailController.text,
                      password: _passwordController.text);
                  Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomePage()));
                } catch (e) {
                  print('Failed to sign in: $e');
                  // Handle errors here
                }
              },
              child: const Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}

Step 3: Integrating Cloud Firestore

Cloud Firestore is a NoSQL document database that allows you to easily store and retrieve data.

Adding Data

Add data to Cloud Firestore:

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

class AddDataPage extends StatelessWidget {
  final _firestore = FirebaseFirestore.instance;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Add Data')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _firestore.collection('users').add({
              'name': 'John Doe',
              'age': 30,
            });
          },
          child: const Text('Add User Data'),
        ),
      ),
    );
  }
}

Reading Data

Read data from Cloud Firestore:

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

class ReadDataPage extends StatelessWidget {
  final _firestore = FirebaseFirestore.instance;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Read Data')),
      body: StreamBuilder(
        stream: _firestore.collection('users').snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }

          final users = snapshot.data?.docs;
          List userWidgets = [];
          if (users != null) {
            for (var user in users) {
              final userData = user.data() as Map;
              final userName = userData['name'];
              final userAge = userData['age'];

              final userWidget = Text('Name: $userName, Age: $userAge');
              userWidgets.add(userWidget);
            }
          }

          return ListView(
            children: userWidgets,
          );
        },
      ),
    );
  }
}

Step 4: Integrating Cloud Functions

Cloud Functions let you run backend code in response to events triggered by Firebase features and HTTPS requests.

Writing a Simple Cloud Function

Here’s how to create a function in TypeScript that sends a welcome email upon user sign-up:

/**
 * Import necessary modules
 */
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();

/**
 * Trigger a function on user creation.
 */
export const sendWelcomeEmail = functions.auth.user().onCreate((user) => {
    const email = user.email;
    const displayName = user.displayName;

    return admin.firestore().collection('emails').add({
        to: email,
        message: {
            subject: 'Welcome to Our App!',
            html: `Hello ${displayName || 'User'}, welcome to our app!`,
        },
    }).then(() => console.log('Queued welcome email for delivery!'));
});

Deploy the Cloud Function

  1. Save the function code in index.ts.
  2. Navigate to the function directory in the terminal and run:
    firebase deploy --only functions

Call the Function From Your Flutter App

To call a function directly via HTTPS, use the httpsCallable method from the firebase_functions package:

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_functions/firebase_functions.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  try {
    final callable = FirebaseFunctions.instance.httpsCallable('yourFunctionName');
    final result = await callable.call({
      'param1': 'value1',
      'param2': 'value2',
    });
    print(result.data);
  } catch (e) {
    print('Failed to call function: $e');
  }
}

Step 5: Using Cloud Storage

Cloud Storage is a service that allows you to store and retrieve media, such as images, videos, or other files.

Upload Files to Cloud Storage

import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

class UploadPage extends StatefulWidget {
  @override
  _UploadPageState createState() => _UploadPageState();
}

class _UploadPageState extends State {
  File? _imageFile;
  final picker = ImagePicker();

  Future getImage() async {
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);

    setState(() {
      if (pickedFile != null) {
        _imageFile = File(pickedFile.path);
      } else {
        print('No image selected.');
      }
    });
  }

  Future uploadFile() async {
    if (_imageFile == null) return;

    final fileName = DateTime.now().toString() + '.jpg';
    final destination = 'images/$fileName';

    try {
      final ref = firebase_storage.FirebaseStorage.instance
          .ref(destination);
      await ref.putFile(_imageFile!);

      // Optionally, you can get the download URL
      final downloadURL = await ref.getDownloadURL();
      print('Download URL: $downloadURL');
    } catch (e) {
      print('Error uploading file: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Upload to Cloud Storage')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _imageFile == null
                ? const Text('No image selected.')
                : Image.file(_imageFile!),
            ElevatedButton(
              onPressed: getImage,
              child: const Text('Select Image'),
            ),
            ElevatedButton(
              onPressed: uploadFile,
              child: const Text('Upload Image'),
            ),
          ],
        ),
      ),
    );
  }
}

Best Practices

  • Error Handling: Implement robust error handling to manage exceptions when calling GCP services.
  • Security: Follow security best practices for handling credentials and sensitive data.
  • Optimized Data Fetching: Optimize data queries and minimize data transfer to improve app performance.

Conclusion

Integrating Google Cloud Platform (GCP) services into Flutter apps opens a plethora of opportunities to create scalable, secure, and feature-rich mobile applications. By using services like Firebase Authentication, Cloud Firestore, Cloud Functions, and Cloud Storage, developers can significantly enhance their apps’ capabilities and overall user experience. The synergy between Flutter and GCP provides a robust framework for modern app development. Experiment with different combinations to harness the full potential of these technologies.