Skip to main content

Creating Flet extension for existing Flutter package

Introduction

While Flet controls leverage many built-in Flutter widgets, enabling the creation of complex applications, not all Flutter widgets or third-party packages can be directly supported by the Flet team or included within the core Flet framework.

To address this, the Flet framework provides an extensibility mechanism. This allows you to incorporate widgets and APIs from your own custom Flutter packages or third-party libraries directly into your Flet application.

In this guide, you will learn how to create Flet extension from template and then customize it to integrate 3rd-party Flutter package into your Flet app.

Prerequisites

To integrate custom Flutter package into Flet you need to have basic understanding of how to create Flutter apps and packages in Dart language and have Flutter development environment configured. See Flutter Getting Started for more information about Flutter and Dart.

Create Flet extension from template

Flet now makes it easy to create and build projects with your custom controls based on Flutter widgets or Flutter 3rd-party packages. In the example below, we will be creating a custom Flet extension based on the flutter_spinkit package.

  1. Create new virtual enviroment and install Flet there.

  2. Create new Flet extension project from template:

flet create --template extension --project-name flet-spinkit

A project with new FletSpinkit control will be created. The control is just a Flutter Text widget with text property, which we will customize later.

  1. Build your app.

Flet project created from extension template has examples/flet_spinkit_example folder with the example app.

When in the folder where your pyproject.toml for the app is (examples/flet_spinkit_example), run flet build command, for example, for macos:

flet build macos -v

Open the app and see the new custom Flet Control:

open build/macos/flet-spinkit-example.app
  1. Change your app.

Once the project was built for desktop once, you can make changes to your python files and run it without re-building.

First, install dependencies from pyproject.toml:

pip install .

Now you can make changes to your example app main.py:

import flet as ft

from flet_spinkit import FletSpinkit


def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER

page.add(
ft.Container(
height=150,
width=300,
alignment=ft.alignment.center,
bgcolor=ft.Colors.PINK_200,
content=FletSpinkit(
tooltip="My new PINK FletSpinkit Control tooltip",
value="My new PINK FletSpinkit Flet Control",
),
),
)


ft.app(main)

and run:

flet run
  1. Re-build your app.
Known issue

There is a known issue that Flet would build with cached files and your changes will not be included. As a temporary solution, you need to clear cache before re-building:

pip cache purge

When you make any changes to your package, you need to re-build:

pip cache purge
flet build macos -v

If you need to debug, run this command:

build/macos/flet-spinkit-example.app/Contents/MacOS/flet-spinkit-example --debug

Integrate 3rd-party Flutter package

For the example purposes we will be integrating flutter_spinkit package into our Flet app.

  1. Add dependency.

Go to src/flutter/flet_spinkit folder and run this command to add dependency to flutter_spinkit to pubspec.yaml:

flutter pub add flutter_spinkit

Read more information about using Flutter packages here.

  1. Modify dart file.

In the src/flutter/flet_spinkit/lib/src/flet_spinkit.dart file, add import statement and replace Text widget with SpinKitRotatingCircle widget:

import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';

class FletSpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;

const FletSpinkitControl({
super.key,
required this.parent,
required this.control,
});

@override
Widget build(BuildContext context) {
Widget myControl = SpinKitRotatingCircle(
color: Colors.red,
size: 100.0,
);


return constrainedControl(context, myControl, parent, control);
}
}
  1. Rebuild your example app.

Go to examples/flet_spinkit_example, clear cache and rebuild your app:

pip cache purge
flet build macos -v
  1. Run your app:

Flet extension structure

After creating new Flet project from extension template, you will see the following folder structure:

├── LICENSE
├── README.md
├── examples
│ └── flet_spinkit_example
│ ├── README.md
│ ├── pyproject.toml
│ └── src
│ └── main.py
├── pyproject.toml
└── src
├── flet_spinkit
│ ├── __init__.py
│ └── flet_spinkit.py
└── flutter
└── flet_spinkit
├── CHANGELOG.md
├── LICENSE
├── README.md
├── lib
│ ├── flet_spinkit.dart
│ └── src
│ ├── create_control.dart
│ └── flet_spinkit.dart
└── pubspec.yaml

Flet extension consists of:

  • package, located in src folder
  • example app, located in examples/flet-spinkit_example folder

Package

Package is the component that will be used in your app. It contists of two parts: Python and Flutter.

Python

flet_spinkit.py

Here you create Flet Python control - a Python class that you use in your Flet app.

The minumal requirements for this class is that it has to be inherited from Flet Control and it has to have _get_control_name method that will return the control name. This name should be the same as args.control.type we check in the create_control.dart file.

Flutter

pubspec.yaml

A yaml file containing metadata that specifies the package's dependencies.

There is already a dependency to flet created from template. You need to add there a dependency to Flutter package for which you are creating your extension.

flet_spinkit.dart

Two methods are exported:

  • createControl - called to create a widget that corresponds to a control on Python side.
  • ensureInitialized - called once on Flet program start.
src/create_control.dart

Creates Flutter widget based on control names returned by the Control's _get_control_name() function. This mechanism iterates through all third-party packages and returns the first matching widget.

src/flet_spinkit.dart

Here you create Flutter "wrapper" widget that will build Flutter widget or API that you want to use in your Flet app.

Wrapper widget passes the state of Python control down to a Flutter widget, that will be displayed on a page, and provides an API to route events from Flutter widget back to Python control.

Example app

src/main.py

Python program that uses Flet Python control.

pyproject.toml

Here you specify dependency to your package, which can be:

  • Path dependency

Absolute path to your Flet extension folder, for example:

dependencies = [
"flet-spinkit @ file:///Users/user-name/projects/flet-spinkit",
"flet>=0.26.0",
]
  • Git dependency

Link to git repository, for example:

dependencies = [
"flet-ads @ git+https://github.com/flet-dev/flet-ads.git",
"flet>=0.26.0",
]
  • PyPi dependency

Name of the package published on pypi.org, for example:

dependencies = [
"flet-ads",
"flet>=0.26.0",
]

Customize properties

In the example above, Spinkit control creates a hardcoded Flutter widget. Now let's customize its properties.

Common properties

Generally, there are two types of controls in Flet:

  1. Visual controls that are added to the app/page surface, such as FletSpinkit.

  2. Non-visual controls that can be:

    • popup controls (dialogs, pickers, panels etc.).

    • services that are added to overlay, such as Video or Audio.

Flet Control class has properties common for all controls such as visible, opacity and tooltip, to name a few.

Flet ConstrainedControl class is inherited from Control and has many additional properties such as top and left for its position within Stack and a bunch of animation properties.

When creating non-visual control, your Python control should be inherited from 'Control. Then, to be able to use Control properties in your app, you need to add them to the constructor of your Python Control. In its dart counterpart (src/flet_spinkit.dart) use baseControl() to wrap your Flutter widget.

When creating visual control, your Python control should be inherited from ConstrainedControl. In its dart counterpart (src/flet_spinkit.dart) use constrainedControl() to wrap your Flutter widget.

Then, to be able to use Control and ConstrainedControl properties in your app, you need to add them to the constructor of your Python Control.

See reference for the common Control properties here.

If you have created your extension project from Flet extension template, your Python Control is already inherited from ConstrainedControl and you can use its properties in your example app:

import flet as ft

from flet_spinkit import FletSpinkit


def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER

page.add(
ft.Stack(
[
ft.Container(height=200, width=200, bgcolor=ft.Colors.BLUE_100),
FletSpinkit(opacity=0.5, tooltip="Spinkit tooltip", top=0, left=0),
]
)
)


ft.app(main)

Control-specific properties

Now that you have taken full advantage of the properties Flet Control and ConstrainedControl offer, let's define the properties that are specific to the new Control you are building.

In the FletSpinkit example, let's define its color and size.

In Python class, define new color and size properties:

from enum import Enum
from typing import Any, Optional

from flet.core.constrained_control import ConstrainedControl
from flet.core.control import OptionalNumber
from flet.core.types import ColorEnums, ColorValue


class FletSpinkit(ConstrainedControl):
"""
FletSpinkit Control.
"""

def __init__(
self,
#
# Control
#
opacity: OptionalNumber = None,
tooltip: Optional[str] = None,
visible: Optional[bool] = None,
data: Any = None,
#
# ConstrainedControl
#
left: OptionalNumber = None,
top: OptionalNumber = None,
right: OptionalNumber = None,
bottom: OptionalNumber = None,
#
# FletSpinkit specific
#
color: Optional[ColorValue] = None,
size: OptionalNumber = None,
):
ConstrainedControl.__init__(
self,
tooltip=tooltip,
opacity=opacity,
visible=visible,
data=data,
left=left,
top=top,
right=right,
bottom=bottom,
)

self.color = color
self.size = size

def _get_control_name(self):
return "flet_spinkit"

# color
@property
def color(self) -> Optional[ColorValue]:
return self.__color

@color.setter
def color(self, value: Optional[ColorValue]):
self.__color = value
self._set_enum_attr("color", value, ColorEnums)

# size
@property
def size(self):
return self._get_attr("size")

@size.setter
def size(self, value):
self._set_attr("size", value)

In src/flet_spinkit.dart file, use helper methods attrColor and attrDouble to access color and size values:

import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';

class FletSpinkitControl extends StatelessWidget {
final Control? parent;
final Control control;

const FletSpinkitControl({
super.key,
required this.parent,
required this.control,
});

@override
Widget build(BuildContext context) {
var color = control.attrColor("color", context);
var size = control.attrDouble("size");
Widget myControl = SpinKitRotatingCircle(
color: color,
size: size ?? 100,
);


return constrainedControl(context, myControl, parent, control);
}
}

Use color and size properties in your app:

import flet as ft

from flet_spinkit import FletSpinkit


def main(page: ft.Page):
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER

page.add(
ft.Stack(
[
ft.Container(height=200, width=200, bgcolor=ft.Colors.BLUE_100),
FletSpinkit(
opacity=0.5,
tooltip="Spinkit tooltip",
top=0,
left=0,
color=ft.Colors.YELLOW,
size=150,
),
]
)
)


ft.app(main)

Re-build and run:

You can find source code for this example here.

Examples for different types of properties and events

Enum properties

For example, clip_behaviour for AppBar.

In Python:

# clip_behavior
@property
def clip_behavior(self) -> Optional[ClipBehavior]:
return self._get_attr("clipBehavior")

@clip_behavior.setter
def clip_behavior(self, value: Optional[ClipBehavior]):
self._set_attr(
"clipBehavior",
value.value if isinstance(value, ClipBehavior) else value,
)

In Dart:

var clipBehavior = Clip.values.firstWhere(
(e) =>
e.name.toLowerCase() ==
widget.control.attrString("clipBehavior", "")!.toLowerCase(),
orElse: () => Clip.none);
Json properties

For example, shape property for Card.

In Python:

def before_update(self):
super().before_update()
self._set_attr_json("shape", self.__shape)

# shape
@property
def shape(self) -> Optional[OutlinedBorder]:
return self.__shape

@shape.setter
def shape(self, value: Optional[OutlinedBorder]):
self.__shape = value

In Dart:

var shape = parseOutlinedBorder(control, "shape")
Children

For example, content for AlertDialog:

In Python:

    def _get_children(self):
children = []
if self.__content:
self.__content._set_attr_internal("n", "content")
children.append(self.__content)
return children

In Dart:

    var contentCtrls =
widget.children.where((c) => c.name == "content" && c.isVisible);
Events

For example, on_click event for ElevatedButton.

In Python:

# on_click
@property
def on_click(self):
return self._get_event_handler("click")

@on_click.setter
def on_click(self, handler):
self._add_event_handler("click", handler)

In Dart:

Function()? onPressed = !disabled
? () {
debugPrint("Button ${widget.control.id} clicked!");
if (url != "") {
openWebBrowser(url,
webWindowName: widget.control.attrString("urlTarget"));
}
widget.backend.triggerControlEvent(widget.control.id, "click");
}
: null;

Examples

Flet has controls that are implemented as built-in extensions and could serve as a starting point for your own controls.