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
- Create an Account on Zapp
- Create a new Flutter project.
- Set up Firebase into a Flutter web project.
- 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 Twitter, Linkedin, 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.