← Back to blog

Build a URL Shortening App with Flutter and Firebase Extensions

Renuka Kelkar

DevRel Advocate

17th April, 2023

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

Introduction

In today’s digital world, URLs are a fundamental part of the internet and play a vital role in enabling users to access online resources. Whether searching for an image, video, blog, or any other online content, a URL or Uniform Resource Locator is a unique address that identifies a web page or resource online; they are the gateway to accessing these resources.

While developing an app or website using Firebase, you likely store many URLs and associated data in Cloud Firestore. However, manually shortening each URL can be a tedious and time-consuming task.

This is where Shorten URLs Extension can help you.

Shorten URLs Extension

Firebase Extensions are prebuilt solutions that can be easily added to your application without the need to research, write or debug the code.

With Shorten URLs Extension, you can automatically shorten all the URLs stored in Cloud Firestore. It is easy to install and can save you a lot of work and time.

Getting started

Before starting, first, we need to:

Create a Cloud Firestore Database

Upgrade Firebase with Blaze plan

To install this extension, your Firebase project must be on the Blaze (pay-as-you-go) plan. You will only be charged for the resources you use. Most Firebase services offer a free tier for low-volume use. Learn more about Firebase billing.

Install Shorten URLs Extension.

Option 1: Using the Firebase CLI

To install and manage extensions, you can also use the Firebase CLI:

Set up a new project directory or navigate to an existing one

Run the below command in your command line

firebase ext:install firebase/firestore-shorten-urls-bitly --project=projectId_or_alias

You can install the Shorten URLs Extension via Firebase CLI, or if you prefer to use a command line like me, run the firebase ext:install command by passing your project ID into Firebase CLI.

Option 2: Using the Firebase Console

To add an extension, please click on the extension tab on the Firebase console and select the extension from the marketplace.

Configure Extension

Cloud Functions location

Specify the location where you want to deploy the functions created for this extension.

Bitly access token

This extension uses Bitly to shorten URLs, so you’ll need to supply your Bitly access token as part of this extension’s installation.

Collection path

Here, we are required to provide a collection path that contains the URLs that you want to shorten.

URL field name

A field name is required to contain the original long URLs you want to shorten.

Short URL field name

Specify the name of the field where you want to store your shortened URLs

After configuring the extension, click the install button and wait 3 to 5 minutes to finish the installation setup.

How does it work?

Behind the scenes, This extension uses Cloud Functions that listen to your specified Cloud Firestore collection. If you add a URL to a specified field in any document within that collection, this extension:

  • Shortens the URL.
  • Saves the shortened URL in a new specified field in the same document.

If the original URL in a document is updated, the shortened URL will also be automatically updated.

Create Collections

Once you have inserted the new URL into the url field in a few seconds, it will automatically create a shortened URL in shortUrl field. To understand this in detail, let’s build the project around this.

Project Overview

This is a simple Flutter app created for demonstration purposes to understand how to implement a Shorten URLs Extension in a Flutter application.

In this project, you can insert a long URL into the URL field, giving you a shortened URL within a few seconds.

Let’s start!

I am using the Zapp online editor to build this project. Zapp is an online Flutter sandbox environment that runs directly in the browser, enabling developers to quickly prototype Flutter applications without requiring a local environment.

Setting up environment

  1. Create an Account on Zapp
  2. Create a new Flutter project.
  3. Set up Firebase into a Flutter web project.
  4. Add all required dependencies.
cloud_firestore: ^4.4.5
  firebase_core:

File Structure

Here, I have created four folders:

Model: It contains data.dart model class for data.

Pages: It contains home.dart

Utils: it contains theme.dart file where all styling is defined.

Data Model

To display data in the app, we must first create a DataModel that consists of a url, shortUrl field and timeStamp field (It helps to sort the URL by the time it was created). Create a new Dart file named data.dart and add the following code:

data.dart

import 'package:cloud_firestore/cloud_firestore.dart';

class DataModel {
  DataModel({
    this.url,
    this.shortUrl,
    this.timeStamp,
  });

  late final String? url;
  late final String? shortUrl;
  late final Timestamp? timeStamp;

  DataModel.fromJson(Map<String, dynamic> json) {
    url = json['url'];
    shortUrl = json['shortUrl'];
    timeStamp = json['timeStamp'];
  }

  Map<String, dynamic> toJson() {
    return {
      'url': url,
      'shortUrl': shortUrl,
       'timeStamp': timeStamp,

    };
  }
}

Theme file

theme.dart is a separate theme file containing all the app styling. It helps to keep main.dart file clean.

theme.dart

class AppTheme {
  
  ThemeData themedata = ThemeData(
       scaffoldBackgroundColor: Colors.yellow[100],
        appBarTheme: AppBarTheme(
            foregroundColor: Colors.white //here you can give the text color
            ),
        textTheme: TextTheme(
          titleLarge:
              TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
          titleMedium:
              TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
        ),
        inputDecorationTheme: const InputDecorationTheme(
          hintStyle: TextStyle(
            color: Colors.black,
          ),
          contentPadding: EdgeInsets.only(top: 12, left: 10),
        ),
        primarySwatch: Colors.red,
        brightness: Brightness.light,
      );
}

main.dart

In the main.dart file, add all Firebase details. It is good practice to hide or avoid committing Firebase details to git.

main.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/pages/home.dart';
import 'package:flutter_app/utils/theme.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
      options: const FirebaseOptions(
          apiKey: "Your Key",
          authDomain: "your-project-id.firebaseapp.com",
          projectId: "your-project-id",
          storageBucket: "your-project-id.appspot.com",
          messagingSenderId: "288649268997",
          appId: "Your AppID",
          measurementId: "G-W5CQDYCEL8"));

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter App',
      //custom theme file for project.
      theme: AppTheme().themedata,
      home: Home(),
      debugShowCheckedModeBanner: false,
    );
  }
}

home.dart

To use the service, enter the long URL on the homepage. After you hit the submit button, the URL field in the Firestore will update, and within a few seconds, you will see a shortened URL for that long URL. You can copy the Shorten Url by selecting it or by clicking the copy icon.

First, we create a type-safe Firestore reference:

final ref = FirebaseFirestore.instance
      .collection('urls')
      //calling withConverter is that the generic type T is set to your custom Model. This means that every subsequent call on the document ref, will work with that type instead of Map<String, dynamic>.
      .withConverter<DataModel>(
        fromFirestore: (snapshot, _) => DataModel.fromJson(
          snapshot.data()!,
        ),
        toFirestore: (model, _) => model.toJson(),
      );

To update the long URL here, we create TextEditingController

 final TextEditingController _textController = TextEditingController();

Here is the full code for the home page:

home.dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app/model/data.dart';
import 'package:flutter_app/utils/colors.dart';

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

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final ref = FirebaseFirestore.instance
      .collection('urls')
      //calling withConverter is that the generic type T is set to your custom Model. This means that every subsequent call on the document ref, will work with that type instead of Map<String, dynamic>.
      .withConverter<DataModel>(
        fromFirestore: (snapshot, _) => DataModel.fromJson(
          snapshot.data()!,
        ),
        toFirestore: (model, _) => model.toJson(),
      );

  Query<DataModel>? query;
  @override
  void initState() {
    query = ref.orderBy("timeStamp", descending: true);
    super.initState();
  }

  /// Controller for updating translation input text.
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return SelectionArea(
      child: Scaffold(
        appBar: AppBar(title: Text("Shorten Url")),
        body: StreamBuilder<QuerySnapshot<DataModel>?>(
          stream: query?.snapshots(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              final snapshotData = snapshot.data;
              return SingleChildScrollView(
                child: Column(
                  children: [
                    Container(
                      height: 50,
                      decoration: BoxDecoration(
                        color: MyColor.myPurple,
                        border: Border.all(
                          color: MyColor.myPurpleDark,
                          width: 2,
                        ),
                      ),
                      child: TextField(
                        style: TextStyle(color: Colors.black),
                        controller: _textController,
                        decoration: InputDecoration(
                          border: InputBorder.none,
                          hintText: 'Enter the long url',
                          contentPadding: EdgeInsets.only(top: 15, left: 10),
                          suffixIcon: IconButton(
                            icon: Icon(Icons.send, color: MyColor.myPurpleDark),
                            onPressed: () async {
                              if (_textController.text == "") {
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text('Enter the URL'),
                                  ),
                                );
                              } else {
                                await ref.add(DataModel(
                                    url: _textController.text,
                                    timeStamp: Timestamp.now()));
                                _textController.clear();
                              }

                              // Clear the text field to prepare for next input.
                              _textController.clear();
                            },
                          ),
                        ),
                      ),
                    ),
                    SizedBox(
                      height: 20,
                    ),
                    Image.network(
                        'https://cdn-icons-png.flaticon.com/512/8899/8899639.png',
                        height: 150,
                        width: 150,
                        fit: BoxFit.cover),
                    SizedBox(
                      height: 30,
                    ),
                    Text(
                      "Shorten URLs",
                      style: TextStyle(
                          fontSize: 20,
                          color: Color.fromARGB(255, 33, 3, 1),
                          fontWeight: FontWeight.bold),
                    ),
                    ListView.builder(
                      shrinkWrap: true,
                      itemCount: snapshotData!.docs.length,
                      itemBuilder: (_, i) {
                        final data = snapshotData.docs[i].data();
                        return Padding(
                          padding: const EdgeInsets.all(10.0),
                          child: Container(
                            decoration: BoxDecoration(
                              color: MyColor.myRed,
                              borderRadius: BorderRadius.all(
                                Radius.circular(10),
                              ),
                            ),
                            child: ListTile(
                              title: Text(
                                data.shortUrl ?? "loading..",
                                style: TextStyle(
                                    color: Color.fromARGB(255, 243, 243, 248),
                                    fontSize: 20,
                                    fontWeight: FontWeight.bold),
                              ),
                              subtitle: Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Text(
                                  data.url ?? "",
                                  style: TextStyle(
                                    color: Color.fromARGB(255, 247, 245, 245),
                                  ),
                                ),
                              ),
                              trailing: IconButton(
                                icon: Icon(Icons.copy,
                                    color: Color.fromARGB(255, 255, 255, 255)),
                                onPressed: () async {
                                  await Clipboard.setData(new ClipboardData(
                                      text: data.shortUrl ?? ""));
                                  ScaffoldMessenger.of(context).showSnackBar(
                                    SnackBar(
                                      content: Text(
                                          'link Copied to your clipboard !'),
                                    ),
                                  );
                                },
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              );
            } else
              return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

Final result

You can check the full example here.

Conclusion

We have successfully created a Flutter app using the Shorten URLs extension. To summarise the article, we completed the following steps:

  • Set up Cloud Firestore
  • Installed the Shorten URLs extension
  • Configured the extension
  • Created a new Flutter project and integrated Firebase into it
  • Created a simple Flutter UI where we can insert a new long URL and show the shortened URL

That’s it! The task is complete.

Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase TwitterLinkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.

Renuka Kelkar

DevRel Advocate

Categories