Open In App

Portfolio Implementation using Flutter Web and Firebase

Last Updated : 04 May, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

In this article, we’ll guide you through building a Custom Linktree-style Portfolio using Flutter Web and deploying it on Firebase. A Linktree is a powerful tool to showcase your online presence - social media, projects, or professional links - all in one sleek, responsive page. With Flutter Web, you can design a visually stunning portfolio that works seamlessly across devices, while Firebase makes deployment and updates effortless.

Portfolio-Implementation-using-Flutter-Web-and-Firebase


Step-by-Step Implementation

Step 1: Create a new Flutter Application

Create a new Flutter application using the command Prompt. To create a new app, write the following command and run it.

flutter create app_name

To know more about it refer this article: Creating a Simple Application in Flutter

Step 2: Adding the Dependency

To add the dependency to the pubspec.yaml file, add  url_launcher as a dependency in the dependencies part of the pubspec.yaml file, as shown below:

Dart
dependencies:
     flutter:
       sdk: flutter
     url_launcher: ^6.3.1

Now, run the command below in the terminal.

flutter pub get

Or

Run the command below in the terminal.

flutter pub add url_launcher

Step 3: Import dependencies

To use libraries, import all of them in the respective .dart file.

import 'package:url_launcher/url_launcher.dart'; 

- Collect Assets

After that, we need to collect all the necessary images and icons that will be used in our Flutter app. You can find all the required social media icons in the GitHub repository if you wish to use the same ones as me. Additionally, you will need to upload a single image of yourself to be displayed in the app.

You should also make the necessary changes to the project's 'pubspec.yaml' file to ensure these assets are recognizable. You need to add or uncomment the lines below:

  assets:
- assets/

Make sure to use proper indentation.

Step 4: Start Coding

Now we will start coding our web app. In the main.dart file the imports necessary dependencies, including the HomePage screen. The MyApp class, as the app's root, configures the app's title and theme. The theme's primary color is set to deep purple. The app's structure is defined using the MaterialApp widget, with a title and theme. The home screen is set to MyHomePage. This code showcases the fundamental setup of a Flutter web app, with a customized theme and a designated home page.

main.dart:

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

import 'Screens/home_page.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Your Name',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}


Now we will code the home page of our application. First, we'll create a 'MyHomePage' widget, which is a 'StatefulWidget'. Inside its state, we define the layout and components of our home page.

There are some points to understand for this application:

  • For responsive design, we detect whether the screen width is greater than 800 pixels to determine if it's a desktop layout.
  • The 'Scaffold' widget serves as the main container. Within it, we place a 'Container' that covers the entire screen with a gradient background.
  • Inside the 'Container', we use a 'Stack' to layer the content. The 'Stack' contains a 'Column' that holds the upper and lower sections of the page.
  • The upper section features an 'Expanded' widget for flexible sizing. It contains a 'Center' widget with a 'Column' of elements: a circular avatar, the developer's name, and a short bio. These elements are styled using various font sizes and weights.
  • The lower section is also wrapped in an 'Expanded' widget. It's a 'Container' with a colored background, rounded top corners, and a vertical layout. Inside it, we have a 'SingleChildScrollView' that contains a 'Column' of social media links and icons. Depending on the screen size, either a 'RowView' or 'ColumnView' layout is used.
  • The 'ColumnView' function returns a vertical arrangement of social media icons, with spacing adjusted for responsiveness.
  • The 'RowView' function returns a horizontal arrangement of social media icons divided into two columns. Each column holds various social media links.
  • The 'geeksforgeeks' is a placeholder for functions or widgets that represent individual social media icons.
  • Finally, the 'Footer' widget is placed at the bottom of the page, offering additional information or navigation.

This comprehensive code snippet constructs a responsive Flutter web page with a visually appealing layout and interactive social media links, tailored to desktop and mobile devices.

home_page.dart:

Dart
import 'package:flutter/material.dart';
import 'package:web_links/widgets/animated_container.dart';
import 'package:web_links/widgets/footer.dart';
import 'package:web_links/widgets/socialCards.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}


class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    bool isDesktop = MediaQuery.of(context).size.width > 800;
    return Scaffold(
      body: Container(
          
        // 100% of height
        height: MediaQuery.of(context).size.height,
        
        // 100% of width
        width: MediaQuery.of(context).size.width, 
        
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [Color(0xff9dc9e9), Color(0xff2059ff)],
          ),
        ),
        child: Stack(
          children: [
            Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.only(top: 20.0),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: const [
                          CircleAvatar(
                            radius: 80,
                            backgroundImage: AssetImage('assets/ankit.png'),
                          ),
                          SizedBox(
                            height: 5,
                          ),
                          Text(
                              
                            // Your Name
                            'Ankit Kumar', 
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 28,
                            ),
                          ),
                          SizedBox(
                            height: 5,
                          ),
                          Text(
                              
                            // Your short bio (optional)
                            'Passionate Flutter Developer', 
                            style: TextStyle(
                              fontWeight: FontWeight.w400,
                              fontStyle: FontStyle.italic,
                              fontSize: 20,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
                const SizedBox(
                  height: 10,
                ),
                Expanded(
                  child: Container(
                    
                    // height: 490,
                    width: MediaQuery.of(context).size.width,
                    decoration: const BoxDecoration(
                      color: Color(0xff171430),
                      shape: BoxShape.rectangle,
                      borderRadius: BorderRadius.vertical(
                        top: Radius.circular(50.0),
                      ),
                    ),
                    child: SingleChildScrollView(
                      child: Column(
                        children: [
                          Center(
                            child: isDesktop ? RowView() : ColumnView(),
                          ),
                          Footer(),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Column ColumnView() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        SizedBox(height: MediaQuery.of(context).size.height * 0.06),
        codewars(),
        geeksforgeeks(),
        github(),
        gmail(),
        hashnode(),
        instagram(),
        playstore(),
        portfolio(),
        resume(),
        twitter(),
        whatsapp(),
        youtube(),
        SizedBox(height: MediaQuery.of(context).size.height * 0.05),
      ],
    );
  }

  Row RowView() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Column(
          children: [
            SizedBox(
              height: MediaQuery.of(context).size.height * 0.05,
            ),
            codewars(),
            geeksforgeeks(),
            github(),
            gmail(),
            hashnode(),
            instagram(),
          ],
        ),
        SizedBox(width: MediaQuery.of(context).size.width * 0.05),
        Column(
          children: [
            SizedBox(
              height: MediaQuery.of(context).size.height * 0.05,
            ),
            playstore(),
            portfolio(),
            resume(),
            twitter(),
            whatsapp(),
            youtube(),
          ],
        ),
        // Instagram
      ],
    );
  }
}


The following functions efficiently create social media cards using the 'CustomAnimatedContainer' widget :

  • Resume: This orange card features a resume image with a direct link to the Google Drive resume.
  • Portfolio: A deep purple card showcases a portfolio image, linking seamlessly to the developer's website.
  • Twitter: The light blue card prominently displays a Twitter image and connects to the developer's Twitter account.
  • GitHub: This dark gray card represents GitHub, providing a link to the developer's repository.
  • LinkedIn: A blue card features a LinkedIn image, linking directly to the developer's LinkedIn page.
  • Instagram: The pink card highlights Instagram, linking to the developer's account with ease.
  • YouTube: A striking red card showcases the YouTube branding, linking straight to the developer's channel.
  • Gmail: A red accent card features a Gmail image, linking to compose an email effortlessly.
  • GeeksForGeeks: This green card highlights GeeksForGeeks, linking to the developer's articles effectively.
  • PlayStore: A light blue accent card showcases the Play Store image, linking to the developer's app with precision.
  • WhatsApp: The green card prominently features WhatsApp branding, linking directly to the developer's contact.


These functions create impactful, reusable, and visually appealing social media cards for a Flutter web app.

socialCards.dart:

Dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/animated_container.dart';

// Resume container with a button
// linking to the latest resume
CustomAnimatedContainer resume() {
  return CustomAnimatedContainer(
      
    // Yellow color theme
    containerColor: Color.fromARGB(255, 245, 176, 3), 
    borderColor: Color.fromARGB(255, 245, 176, 3),
    imagePath: 'assets/resume.png',
    text: 'Latest Resume',
    
    // Add your resume link here
    linkUrl: '', 
  );
}

// Portfolio container with a button
// linking to the portfolio
CustomAnimatedContainer portfolio() {
  return CustomAnimatedContainer(
      
    // Purple theme for portfolio
    containerColor: Colors.deepPurpleAccent, 
    borderColor: Colors.deepPurpleAccent,
    imagePath: 'assets/portfolio.png',
    text: 'Portfolio',
    
    // Add your portfolio link here
    linkUrl: '', 
  );
}

// Twitter container with a button
// linking to Twitter profile
CustomAnimatedContainer twitter() {
  return CustomAnimatedContainer(
      
    // Blue theme for Twitter
    containerColor: Colors.lightBlue, 
    borderColor: Colors.lightBlue,
    imagePath: 'assets/x.png',
    text: 'Twitter',
    
    // Add your Twitter profile link here
    linkUrl: '', 
  );
}

// GitHub container with a button
// linking to GitHub profile
CustomAnimatedContainer github() {
  return CustomAnimatedContainer(
      
    // Dark theme for GitHub
    containerColor: Colors.black38, 
    borderColor: Colors.black38,
    imagePath: 'assets/git.png',
    
    // Fixed typo from 'GtiHub' to 'GitHub'
    text: 'GitHub',
    
    // Add your GitHub profile link here
    linkUrl: '', 
  );
}

// LinkedIn container with a button
// linking to LinkedIn profile
CustomAnimatedContainer linkedin() {
  return CustomAnimatedContainer(
      
    // Blue theme for LinkedIn
    containerColor: Colors.blueAccent, 
    borderColor: Colors.blueAccent,
    imagePath: 'assets/linkedin.png',
    text: 'LinkedIn',
    
    // Add your LinkedIn profile link here
    linkUrl: '', 
  );
}

// Instagram container with a button
// linking to Instagram profile
CustomAnimatedContainer instagram() {
  return CustomAnimatedContainer(
    
    // Pink theme for Instagram
    containerColor: Colors.pink, 
    borderColor: Colors.pink,
    imagePath: 'assets/insta.png',
    text: 'Instagram',
    
    // Add your Instagram profile link here
    linkUrl: '', 
  );
}

// YouTube container with a button
// linking to YouTube channel
CustomAnimatedContainer youtube() {
  return CustomAnimatedContainer(
      
    // Red theme for YouTube
    containerColor: Colors.red, 
    borderColor: Colors.red,
    imagePath: 'assets/youtube.png',
    text: 'YouTube',
    
    // Add your YouTube channel link here
    linkUrl: '', 
  );
}

// Gmail container with a button
// linking to an email address
CustomAnimatedContainer gmail() {
  return CustomAnimatedContainer(
      
    // Red theme for Gmail
    containerColor: Colors.redAccent, 
    borderColor: Colors.redAccent,
    imagePath: 'assets/mail.png',
    text: 'Gmail',
    
    // Add your email address here
    linkUrl: 'mailto:[email protected]', 
  );
}

// GeeksForGeeks container with a button
// linking to GeeksForGeeks website
CustomAnimatedContainer geeksforgeeks() {
  return CustomAnimatedContainer(
      
    // Green theme for GeeksForGeeks
    containerColor: Colors.green, 
    borderColor: Colors.green,
    imagePath: 'assets/gfg.png',
    text: 'GeeksForGeeks',
    
    // Add your GFG profile link here
    linkUrl: 'https://www.geeksforgeeks.org', 
  );
}

// PlayStore container with a button
// linking to Google Play Store
CustomAnimatedContainer playstore() {
  return CustomAnimatedContainer(
      
    // Blue theme for Play Store
    containerColor: Colors.lightBlueAccent, 
    borderColor: Colors.lightBlueAccent,
    imagePath: 'assets/playstore.png',
    text: 'PlayStore',
    
    // Add your Play Store app link here
    linkUrl: '', 
  );
}

// WhatsApp container with a button
// linking to WhatsApp chat
CustomAnimatedContainer whatsapp() {
  return CustomAnimatedContainer(
      
    // Green theme for WhatsApp
    containerColor: Colors.green, 
    borderColor: Colors.green,
    imagePath: 'assets/whatsapp.png',
    text: 'WhatsApp',
    
    // Add your WhatsApp contact link here
    linkUrl: '', 
  );
}


Now, we shall see the code for the CustomAnimatedContainer, which is the widget that is used as the instance for each social media function above. The code below defines a 'CustomAnimatedContainer' widget, which is a stateful widget. This widget is designed to create interactive animated containers representing social media links. It takes several parameters for customization: 'containerColor' (background color), 'borderColor', 'imagePath' (path to the image), 'text' (displayed text), and 'linkUrl' (URL to be opened when clicked).

  • The '_CustomAnimatedContainerState' class manages the state of the widget. It has a '_isHovered' boolean variable to keep track of whether the container is being hovered over by the mouse.
  • In the 'build' method, the widget renders a 'Container' that enforces minimum height and width constraints and adds a bottom margin. The 'InkWell' widget wraps the content of the container and handles the tap action to open the specified 'linkUrl' in a browser.
  • Inside the 'InkWell', the 'MouseRegion' widget tracks mouse enter and exit events to update the '_isHovered' state. When the mouse enters the container, '_isHovered' is set to 'true', and when it exits, '_isHovered' is set to 'false'.
  • The 'AnimatedContainer' within the 'MouseRegion' is where the visual representation of the social media link is created. It changes appearance smoothly when '_isHovered' changes. It has a dynamic width and height based on the screen size. It also has a background color defined by 'containerColor', a rounded corner, and a border that changes color based on '_isHovered'. A subtle shadow is added for depth.

The content of the container is composed of a 'Row' with three main elements:

  1. An 'Image.asset' widget displays the social media icon. The size of the icon is based on the screen size.
  2. A 'Text' widget displays the 'text' of the link, with white color, font weight, and font size based on the screen size.
  3. An 'AnimatedSwitcher' that shows different icons depending on '_isHovered'. It either displays a "touch" icon or an arrow icon.

The code creates a highly customizable, interactive, and visually appealing social media link container that smoothly transitions between states when hovered over or clicked, enhancing the user experience of the Flutter web app.

animated_container.dart:

Dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

class CustomAnimatedContainer extends StatefulWidget {
  final Color containerColor;
  final Color borderColor;
  final String imagePath;
  final String text;
  final String linkUrl;

  CustomAnimatedContainer({
    required this.containerColor,
    required this.borderColor,
    required this.imagePath,
    required this.text,
    required this.linkUrl,
  });

  @override
  _CustomAnimatedContainerState createState() =>
      _CustomAnimatedContainerState();
}

class _CustomAnimatedContainerState extends State<CustomAnimatedContainer> {
  bool _isHovered = false;

  @override
  Widget build(BuildContext context) {
    return Container(
        
      // constrains of min height and width
      constraints: BoxConstraints(
        minHeight: 50,
        minWidth: 250,
      ),
      margin:
          EdgeInsets.only(bottom: MediaQuery.of(context).size.height * 0.02),
      child: InkWell(
        onTap: () {
            
          // Open the link when the container is clicked
          launch(widget.linkUrl);
        },
        child: MouseRegion(
          onEnter: (_) {
            setState(() {
              _isHovered = true;
            });
          },
          onExit: (_) {
            setState(() {
              _isHovered = false;
            });
          },
          child: AnimatedContainer(
            duration: Duration(milliseconds: 400),
            height: MediaQuery.of(context).size.height * 0.063,
            width: MediaQuery.of(context).size.width * 0.13,
            decoration: BoxDecoration(
              color: widget.containerColor,
              borderRadius: BorderRadius.circular(15),
              border: Border.all(
                color: _isHovered ? widget.borderColor : Colors.white,
                width: 2,
              ),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.5),
                  spreadRadius: 1,
                  blurRadius: 5,
                  offset: const Offset(0, 3),
                ),
              ],
            ),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Expanded(
                  flex: 3,
                  child: Container(
                    padding: EdgeInsets.all(1),
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.white,
                    ),
                    child: Image.asset(
                      widget.imagePath,
                      height: MediaQuery.of(context).size.height * 0.04,
                      width: MediaQuery.of(context).size.width * 0.04,
                    ),
                  ),
                ),
                SizedBox(
                  width: MediaQuery.of(context).size.width * 0.002,
                ),
                Expanded(
                  flex: 8,
                  child: Text(
                    widget.text,
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.w500,
                      fontSize: MediaQuery.of(context).size.height * 0.02,
                    ),
                  ),
                ),
                AnimatedSwitcher(
                  duration: Duration(milliseconds: 400),
                  child: _isHovered
                      ? Icon(
                          Icons.touch_app_outlined,
                          key: ValueKey<bool>(_isHovered),
                          color: Colors.white,
                        )
                      : Icon(
                          Icons.arrow_forward_ios,
                          key: ValueKey<bool>(_isHovered),
                          color: Colors.white,
                        ),
                ),
                SizedBox(
                  width: MediaQuery.of(context).size.width * 0.005,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}


We also used a widget instance called Footer on our home page, so we shall see what its code looks like. The code defines a Footer widget for a Flutter web app. It features clickable icons and links. The _FooterState class manages its state, tracking hover, and containing a GitHub repository URL.

In the build method, an InkWell widget handles taps and hover events. Icons change color on hover. The footer displays "Made with" text, icons, and the developer's name.

The _launchUrl function launches URLs using url_launcher, handling launch success and errors. This interactive footer enhances user experience, providing GitHub attribution and easy URL access.

footer.dart:

Dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;

class Footer extends StatefulWidget {
  @override
  _FooterState createState() => _FooterState();
}

class _FooterState extends State<Footer> {
  String url = '';
  bool isHovered = false;

  @override
  Widget build(BuildContext context) {
    return InkWell(
        
      // Open the link when the container is clicked
      onTap: () {
        _launchUrl(Uri.parse(url));
      },
      onHover: (value) {
          
        // Handle hover state
        setState(() {
          isHovered = value;
        });
      },
      child: Container(
          
        // color: Colors.black,
        margin: EdgeInsets.only(top: 20, bottom: 20),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _buildIcon(Icons.devices, isHovered),
            SizedBox(width: 5),
            Text(
              'Made with',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(width: 5),
            _buildIcon(Icons.star, isHovered),
            SizedBox(width: 5),
            Text(
              'Flutter by GFG',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildIcon(IconData icon, bool isHovered) {
    return Icon(
      icon,
      
      // Change the color based on hover state
      color: isHovered ? Colors.amber : Colors.white,
    );
  }

  void _launchUrl(Uri url) async {
    if (await url_launcher.canLaunch(url.toString())) {
      await url_launcher.launch(url.toString());
    } else {
      throw 'Could not launch $url';
    }
  }
}


Now that we are done we can use the command below to check our app, and before doing so select the device as Chrome or any other browser. But you can also check it on a mobile device.

flutter run

To make a build compatible with the web i.e. flutter web use the command below:

flutter build web --release -v

You can see the file in the build folder which would contain a folder named web. This is the content that will be uploaded to Firebase.


Step 5: Firebase Deployment

Here I will give a quick overview of how to deploy the app we have created on Firebase hosting. But for more details you can check out this article here: Hosting Flutter Website On Firebase For Free

1. Create a new project on the Firebase console.


2. Register the app using the preferred method. In my case, I have simply used the method of adding a script in my index.html file.

3. Install Firebase CLI using the command command:

npm install -g firebase-tools

Just make sure you have the proper version of node installed on your system.

4. Initiate Firebase by using the below command but make sure you are logged in to Firebase with the correct credentials:

// login
firebase login

// starting services
firebase init

Choose hosting with a spacebar and move forward with the project that you just created. It will ask to create a public folder and we need to put all the content of the Flutter web build in this.

And use the command below the deploy it to the firebase. When successful it would give you the URL where you can asses that!

// replace Project ID with your  project ID
firebase deploy --only Project ID

It's all done! Now is the time to flaunt your newly custom-created weblinks with Flutter! Enjoy!

For the Complete Application code refer to this Link : Click Here



Next Article

Similar Reads