Build a URL Shortener Flutter App With Flet Python Framework
Tags: flet, python, flutter, webdev
Published: 2022-12-19 13:25:11

Feel free to scroll down to the end of this article to look at the final result. :)

## Project’s Dependencies

For this project, we will need just two python packages: [Flet](https://flet.dev/docs/guides/python/getting-started#installing-flet-module) and [Pyshorteners](https://pyshorteners.readthedocs.io/en/latest/index.html).

Flet will be the frontend person in charge of the building and rendering of the UI, while Pyshorteners will be the backend person in charge of shortening the given URLs/links.

From this, you can already understand that Flet could be combined with any Python library of your choice (pyshorteners, in our case).

## Installing our Dependencies

Please make sure you have Python (preferably v3.7 or above) installed, then run these two commands in your terminal/command line:

```bash
pip install flet
pip install pyshorteners
```

I recommend you do this in a Virtual Machine (VM).

After the installations are done, create a new file and name it as you wish (ex: my-url-shortener) and run this basic “Hello, World!” code using your favorite IDE to ensure you are good to go.

```python
import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello, world!"))
    
ft.app(target=main)
```
![Hello World — Dark Mode](https://miro.medium.com/max/640/1*RRh_RTBij6QHRP-Wcg9lqA.webp)
![Hello World — Light Mode](https://miro.medium.com/max/640/1*Yy878iMvB8JMfko1I8_hNw.webp)

Throughout this article, I will be importing Flet with the alias `ft`, just like Numpy is commonly aliased `np` or Pandas `pd`.

The execution of that code block opens a native window containing the rendered output. Because we will deal with links, let’s render the output in our web browser. To do that, modify the last line of code, as shown below:

```python
ft.app(target=main, view=ft.WEB_BROWSER)  # add 'port' parameter to specify the port to be used
```

![Hello World in Browser — Dark Mode](https://miro.medium.com/max/720/1*mCxaDB5ldD3ybOsPsEPiKQ.webp)
![Hello World in Browser — Light Mode](https://miro.medium.com/max/720/1*WbxVtytvEDZ2PdJ3Whpc9g.webp)

## The User Interface (UI)
The UI for this project will be straightforward. We will need a text field to get the user input (URL/link), with a button to validate that input.
This input will be sent to the backend for processing, and the output will be displayed as text on the screen.

We will also have a button to copy the generated shortened links and another to open/view the link in a browser tab. Exciting, right?

## Application’s Base
Let’s give our application a title and add an app bar.

```python
import flet as ft

def main(page: ft.Page):
    page.title = "URL Shortener"   # title of application/page
    page.horizontal_alignment = "center"     # center our page's content (remove or change at wish)
    
    page.appbar = ft.AppBar(
        title=ft.Text("URL Shortener", color=ft.colors.WHITE),    # title of the AppBar, with a white color
        center_title=True,          # we center the title
        bgcolor=ft.colors.BLUE,     # a color for the AppBar's background
    )
    
    page.add(ft.Text(value="Hello, world!"))    # some placeholder content (nothing will be shown without this line)
    
ft.app(target=main, view=ft.WEB_BROWSER)  # add 'port' parameter to specify the port to be used
```
![Hello World in the browser with title and AppBar — Dark Mode](https://miro.medium.com/max/640/1*sIAVX89oyhN0bWi03fsIgQ.webp)

![Hello World in the browser with title and AppBar — Light Mode](https://miro.medium.com/max/640/1*iLEz6O7Q_LRIjsV5NeFyfw.webp)

After executing your program, you should see something similar to one of the images above.

‘One of the images’ because [Flet uses your system’s theme by default](https://flet.dev/docs/controls/page#theme_mode). For consistency, let’s use the light theme. So, let’s explicitly set the `theme_mode`.

```python
#...
def main(page: ft.Page):
    page.theme_mode = "light"    # there are only 3 possible values: "dark", "light" and "system"
    #...
```
I omit some code lines (using #…) to save some space.

## Getting the User’s Input
I mentioned earlier that we will use a text field for this.

Let’s create one, store it in a variable named `text_field`, and add it to our page/UI.

```python
  # ...
    page.appbar = ft.AppBar(
        # ...
    )

    text_field = ft.TextField(
        value='https://github.com/ndonkoHenri',  # a test link
        label="Long URL",  # the field's label
        hint_text="type long url here",  # the field's hint-text
        max_length=200,  # the maximum length of inputted links
        keyboard_type="url",  # the field's  keyboard type
        suffix=ft.FilledButton("Shorten!"),  # a button in the field, to shorten the inputted links
    )

    page.add(
        text_field,
        ft.Text("Generated URLs:", weight="bold", size=23)
    )  # add our text field to the page/UI

# ...
```

After execution of your program now, you should see something similar to this:

![Textfield for user input collection](https://miro.medium.com/max/720/1*p8cNS6qhI67H19VwA2ZZ1g.webp)

## Output Display
Before moving on, let’s import pyshorteners below Flet and initialize it.

```python
import flet as ft
import pyshorteners  # pip install pyshorteners

shortener = pyshorteners.Shortener()    # create an instance (initialization)
# ...
```

Now, when shortening an entered long URL, we will display a `Row` containing three main things: a shortened link, a copy button, and a button to open the shortened link in a browser tab.

This `Row` will be displayed several times, so we need to create an isolated and reusable component. We will create a Python class and place it below the `shortener` variable.

```python
# ...

class ShortLinkRow(ft.Row):
    # a row containing the shortened url, and two buttons ('copy', and 'open in browser')

    def __init__(self, shortened_link, link_source):
        """
        We create a new class called `ShortenedLinkRow` that inherits from `ft.Row`.
        The constructor takes two arguments/parameters: `shortened_link` and `source`.

        :param shortened_link: the shortened link
        :param link_source: the service hosting the shortened_link
        """
        super().__init__()  # required when overwriting the constructor

        self.tooltip = link_source  # set the tooltip of the row itself to the link provider/source
        self.alignment = "center"   # center the contents of this row

        # the controls/content of our Row
        self.controls = [
            ft.Text(value=shortened_link, size=16, selectable=True, italic=True),
            ft.IconButton(
                icon=ft.icons.COPY, # the icon to be showed
                on_click=lambda e: self.copy(shortened_link),   # when this button is clicked, call the `copy` method, passing the shortened link as parameter
                bgcolor=ft.colors.BLUE_700,
                tooltip="copy"  # to be showed when hovering on this button
            ),
            ft.IconButton(
                icon=ft.icons.OPEN_IN_BROWSER_OUTLINED, # the icon to be showed
                tooltip="open in browser",  # to be showed when hovering on this button
                on_click=lambda e: e.page.launch_url(shortened_link)    # when this button is clicked, open a browser tab with that shortened link
            )
        ]

    def copy(self, value):
        """
        It copies the given value to the clipboard, and opens a Snackbar to inform the user.
        :param value: The value to be copied to the clipboard
        """
        self.page.set_clipboard(value)
        self.page.show_snack_bar(
            ft.SnackBar(
                ft.Text("Link copied to clipboard!"),
                open=True
            )
        )
```
Having this done, we now need to listen to events in the Textfield so that we can show the outputs at the right moment.

We will listen to the click of the ‘Submit’ button and press the ‘Enter’ keyboard key when the Text field is in focus. When one of these events occurs, Flet will run a particular function named `shorten`, which we will create in a while.

Make the modifications below to your `text_field` variable:

```python
text_field = ft.TextField(
        # ...
        suffix=ft.FilledButton("Shorten!", on_click=shorten),   # the function to be called when this button will be clicked
        on_submit=shorten  # the function to be called once the user presses the Enter keyboard key to submit entry
    )
```
Now let’s create the callback function, and add it to the main function.

```python
# ...(below page.appbar assignment)

def shorten(e):
    """Grabs the URL in the textfield, and displays shortened versions of it."""

    user_link = text_field.value     # retrieve the content of the textfield
    
    if user_link:  # if the textfield is not empty
        # if the entered text in the textfield is not a valid URl, the program may break, hence the need to catch that
        try:
            page.add(ft.Text(f"Long URL: {user_link}", italic=False, weight='bold'))
            page.add(ShortLinkRow(shortened_link=shortener.tinyurl.short(user_link), link_source="By tinyurl.com"))
            page.add(ShortLinkRow(shortened_link=shortener.chilpit.short(user_link), link_source="By chilp.it"))
            page.add(ShortLinkRow(shortened_link=shortener.clckru.short(user_link), link_source="By clck.ru"))
            page.add(ShortLinkRow(shortened_link=shortener.dagd.short(user_link), link_source="By da.dg"))

        except Exception as exception:  # the error might be that a url shortening service from pyshorteners failed to shorten our url
            print(exception)
            # inform the user that an error has occurred
            page.show_snack_bar(
                ft.SnackBar(
                    ft.Text("Sorry, but an error occurred. Please retry, or refresh the page."),
                    open=True
                )
            )

    else:  # if the textfield is empty (no text)
        # inform the user
        page.show_snack_bar(
            ft.SnackBar(
                ft.Text("Please enter a URL in the field!"),
                open=True
            )
        )
# ...
```
If you got lost along the line, please check this [gist for the complete code](https://gist.github.com/ndonkoHenri/7a11c1d6bd39e6d06619d3c76e01b9af). Below is a capture of the final result:
![Final](https://miro.medium.com/max/720/1*2tC8MSfoB_z5zWpXQdy-qA.gif)

I deployed an online version of this app [here](https://url-shorten.fly.dev/) for you. Test it out and let me please have your impressions.

Note that you could eventually [package this app as a standalone executable file](https://flet.dev/docs/guides/python/packaging-desktop-app) or [deploy it on the web](https://flet.dev/docs/guides/python/deploying-web-app). That’s just the cross-platform nature of Flet.

## It’s Your Turn
To make this project better or more advanced, try this out:

- add the possibility to [scroll the page](https://flet.dev/docs/controls/page#scroll) vertically
- expose more [URL shortening APIs from pyshorteners](https://pyshorteners.readthedocs.io/en/latest/apis.html) (I only exposed four in this article, but more than ten are available)
- [change the fonts](https://flet.dev/docs/controls/page#fonts) used in the application.

You can use [this](https://github.com/ndonkoHenri/Flet-Samples/tree/master/URL%20shortener) as a reference/solution if you get stuck. Please, let me know if you tried it or have any questions. I will be happy to help.

Thanks for reading!