-
Notifications
You must be signed in to change notification settings - Fork 583
Description
Duplicate Check
- I have searched the opened issues and there are no duplicates
Describe the requested feature
We need a tool (fletpkg) that automates the integration of Flutter packages (from pub.dev) into Flet applications. The tool should handle all steps—downloading the package, parsing its Dart code, generating Python wrappers, creating custom Flet control classes, setting up dependencies, and generating a ready-to-run Flet app (main.py). The process should be seamless, requiring no manual intervention beyond running a single command.
Goals
Automation: Users should only need to run one command (e.g., fletpkg integrate <package_name>).
Comprehensive: The tool should handle all aspects of integration, including dependency management, code generation, and app setup.
Well-Structured: The generated code and project structure should follow best practices for Flet and Python.
User-Friendly: Even beginners should be able to use the tool without needing to understand Dart, Flutter, or Flet internals.
Architecture
The tool (fletpkg) will be a Python CLI script that performs the following steps:
Package Download: Fetch the specified Flutter package from pub.dev.
Dart Parsing: Analyze the package’s Dart code to extract public APIs (functions, classes, parameters, etc.).
Wrapper Generation: Generate a Python wrapper class that extends flet.Control and maps to the Dart APIs.
Dart-Python Bridge: Set up a WebSocket-based bridge to call Dart functions from Python.
Dependency Setup: Automatically configure pubspec.yaml and other dependencies.
App Generation: Create a main.py Flet app with a sample UI that demonstrates the package’s functionality.
Project Structure: Organize all generated files into a well-structured project.
flet_project/
├── flutter/
│ └── main.dart # Custom Flutter app with Dart-Python bridge
├── wrappers/
│ └── _wrapper.py # Generated Python wrapper class
├── pubspec.yaml # Flutter dependencies (e.g., url_launcher, flet)
├── main.py # Generated Flet app with sample UI
└── fletpkg.py # The CLI tool itself (can be separate)
Suggest a solution
Detailed Implementation Steps
Step 1: CLI Command Setup
Command: fletpkg integrate <package_name>
Example: fletpkg integrate url_launcher
Use Python’s argparse to create a CLI with a single integrate command.
The command should orchestrate the entire process.
Step 2: Package Download
Input: Package name (e.g., url_launcher).
Process:
Create a pubspec.yaml file in the project root with the package as a dependency:
yaml
name: flet_app
description: A Flet app with integrated Flutter package
environment:
sdk: '>=2.18.0 <4.0.0'
dependencies:
flet: ^0.23.0
<package_name>: any
Run flutter pub get to download the package.
Locate the package in the global pub cache (e.g., ~\AppData\Local\Pub\Cache\hosted\pub.dev<package_name>-).
Step 3: Dart Parsing
Goal: Extract public functions, their parameters, and return types from the Dart code.
Process:
Traverse the package’s lib/ directory (e.g., url_launcher-/lib/).
Use a regex or Dart parser (e.g., analyzer package) to extract:
Public function names (e.g., launchUrl).
Parameter names and types (e.g., Uri url, LaunchOptions options).
Return types (e.g., Future).
Filter out private methods (starting with _) and non-functions (e.g., classes, variables).
Return a structured list of APIs, e.g.:
python
[
{"name": "launchUrl", "params": [{"name": "url", "type": "Uri"}], "return": "Future"},
{"name": "canLaunchUrl", "params": [{"name": "url", "type": "Uri"}], "return": "Future"}
]
Step 4: Generate Python Wrapper
Goal: Create a Python class that extends flet.Control and maps to the Dart APIs.
Process:
Generate a file wrappers/<package_name>_wrapper.py.
Create a class (e.g., UrlLauncher) with:
A constructor that initializes the Flet control.
A _call_dart method to send commands to Dart via WebSocket.
Methods for each parsed Dart function, mapping Python calls to Dart.
Example for url_launcher:
python
# wrappers/url_launcher_wrapper.py
import flet as ft
import json
class UrlLauncher(ft.Control):
def __init__(self, **kwargs):
super().__init__()
self._package = "url_launcher"
for k, v in kwargs.items():
setattr(self, k, v)
def _call_dart(self, method: str, params: dict):
message = {
"method": method,
"params": params
}
self.page.client_storage.set("dart_call", json.dumps(message))
self.page.pubsub.send_all("call_dart")
def launchUrl(self, url: str, **kwargs):
params = {"url": url, "kwargs": kwargs}
self._call_dart("launchUrl", params)
def canLaunchUrl(self, url: str, **kwargs):
params = {"url": url, "kwargs": kwargs}
self._call_dart("canLaunchUrl", params)
Step 5: Set Up the Dart-Python Bridge
Goal: Allow Python to call Dart functions via Flet’s WebSocket.
Process:
Create a flutter/main.dart file:
dart
import 'dart:convert';
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:<package_name>/<package_name>.dart' as pkg;
void main() async {
await runAppWithFlet(
(pageClient) async {
return MaterialApp(
home: FletApp(pageClient: pageClient),
);
},
onMessage: (message, sendResponse) async {
if (message["topic"] == "call_dart") {
String? dartCall = pageClient.clientStorage.get("dart_call");
if (dartCall != null) {
Map<String, dynamic> callData = jsonDecode(dartCall);
String method = callData["method"];
Map<String, dynamic> params = callData["params"];
if (method == "launchUrl") {
String url = params["url"];
await pkg.launchUrl(Uri.parse(url));
} else if (method == "canLaunchUrl") {
String url = params["url"];
bool canLaunch = await pkg.canLaunchUrl(Uri.parse(url));
sendResponse({"result": canLaunch});
}
}
}
},
);
}
This Dart code:
Listens for call_dart messages from Python.
Parses the method name and parameters.
Calls the corresponding function from the package (e.g., pkg.launchUrl).
Step 6: Generate a Sample Flet App (main.py)
Goal: Create a main.py that demonstrates the package’s functionality.
Process:
Generate main.py with a simple UI that uses the wrapper class.
Example for url_launcher:
python
import flet as ft
from wrappers.url_launcher_wrapper import UrlLauncher
def main(page: ft.Page):
page.title = "URL Launcher Test"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
url_launcher = UrlLauncher()
url_input = ft.TextField(label="Enter URL", value="https://www.google.com")
def on_launch(e):
url = url_input.value
url_launcher.launchUrl(url)
page.add(ft.Text(f"Launched URL: {url}"))
page.add(
ft.Column(
[
ft.Text("URL Launcher Demo", size=20),
url_input,
ft.ElevatedButton("Launch URL", on_click=on_launch),
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
)
)
if name == "main":
ft.app(target=main)
Step 7: Ensure Dependencies and Run
Goal: Make sure the app runs without manual setup.
Process:
Ensure flet and flutter are installed and accessible.
The pubspec.yaml already includes the package and flet as dependencies.
Users can run the app with:
bash
flet run main.py
Screenshots
No response
Additional details
import argparse
import os
import subprocess
import re
import json
def parse_dart(package_name):
"""Scan Dart files for public APIs."""
print(f"🔎 Parsing Dart files for '{package_name}'")
global_cache = os.path.expanduser(r"~\AppData\Local\Pub\Cache\hosted\pub.dev")
package_dir = None
if os.path.exists(global_cache):
for d in os.listdir(global_cache):
if d.startswith(f"{package_name}-"):
package_dir = os.path.join(global_cache, d, "lib")
break
if not package_dir or not os.path.exists(package_dir):
print(f"❌ Could not find '{package_name}' in pub cache")
return []
public_functions = set()
for root, _, files in os.walk(package_dir):
for file in files:
if file.endswith(".dart"):
file_path = os.path.join(root, file)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
matches = re.findall(r"(?:Future<\w+>|\w+)\s+([a-zA-Z]\w*)\s*\([^)]*\)\s*(?:async\s*)?(?:=>|\{)", content)
for match in matches:
if not match.startswith("_") and match not in ["class", "enum", "typedef", "PlatformException", "Function"]:
public_functions.add(match)
public_functions = sorted(public_functions)
print(f"✅ Found public functions: {public_functions}")
return public_functions
def setup_project(package_name):
"""Set up the project structure and dependencies."""
print(f"📁 Setting up project for '{package_name}'")
# Create pubspec.yaml
with open("pubspec.yaml", "w") as f:
f.write(f'''
name: flet_app
description: A Flet app with integrated Flutter package
environment:
sdk: '>=2.18.0 <4.0.0'
dependencies:
flet: ^0.23.0
{package_name}: any
''')
# Run flutter pub get
print(f"📦 Running 'flutter pub get' for '{package_name}'")
result = subprocess.run(
[r"C:\src\flutter_windows_3.19.6-stable\flutter\bin\flutter.bat", "pub", "get"],
capture_output=True,
text=True
)
if result.returncode != 0:
print("❌ Error:")
print(result.stderr)
return False
print(f"✅ Successfully fetched '{package_name}' from pub.dev")
return True
def generate_wrapper(package_name, functions):
"""Generate Python wrapper for the package."""
print(f"🧠 Generating Python wrapper for: {package_name}")
wrapper_code = f'''
Auto-generated wrapper for {package_name}
import flet as ft
import json
class {package_name.title().replace("_", "")}(ft.Control):
def init(self, **kwargs):
super().init()
self._package = "{package_name}"
for k, v in kwargs.items():
setattr(self, k, v)
def _call_dart(self, method: str, params: dict):
message = {{
"method": method,
"params": params
}}
self.page.client_storage.set("dart_call", json.dumps(message))
self.page.pubsub.send_all("call_dart")
'''
for func in functions:
wrapper_code += f'''
def {func}(self, *args, **kwargs):
params = {{"args": list(args), "kwargs": kwargs}}
self._call_dart("{func}", params)
'''
os.makedirs("wrappers", exist_ok=True)
output_file = f"wrappers/{package_name}_wrapper.py"
with open(output_file, "w") as f:
f.write(wrapper_code)
print(f"✅ Wrapper generated at {output_file}")
def generate_dart_bridge(package_name):
"""Generate the Dart-Python bridge in main.dart."""
print(f"🌉 Generating Dart-Python bridge for '{package_name}'")
dart_code = f'''
import 'dart:convert';
import 'package:flet/flet.dart';
import 'package:flutter/material.dart';
import 'package:{package_name}/{package_name}.dart' as pkg;
void main() async {{
await runAppWithFlet(
(pageClient) async {{
return MaterialApp(
home: FletApp(pageClient: pageClient),
);
}},
onMessage: (message, sendResponse) async {{
if (message["topic"] == "call_dart") {{
String? dartCall = pageClient.clientStorage.get("dart_call");
if (dartCall != null) {{
Map<String, dynamic> callData = jsonDecode(dartCall);
String method = callData["method"];
Map<String, dynamic> params = callData["params"];
// Handle {package_name} methods
if (method == "launchUrl") {{
String url = params["args"][0];
await pkg.launchUrl(Uri.parse(url));
}}
}}
}}
}},
);
}}
'''
os.makedirs("flutter", exist_ok=True)
with open("flutter/main.dart", "w") as f:
f.write(dart_code)
print("✅ Dart bridge generated at flutter/main.dart")
def generate_main_app(package_name):
"""Generate a sample Flet app in main.py."""
print(f"🎨 Generating sample Flet app for '{package_name}'")
main_code = f'''
import flet as ft
from wrappers.{package_name}wrapper import {package_name.title().replace("", "")}
def main(page: ft.Page):
page.title = "{package_name.title()} Test"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
{package_name} = {package_name.title().replace("_", "")}()
url_input = ft.TextField(label="Enter URL", value="https://www.google.com")
def on_launch(e):
url = url_input.value
{package_name}.launchUrl(url)
page.add(ft.Text(f"Launched URL: {{url}}"))
page.add(
ft.Column(
[
ft.Text("{package_name.title()} Demo", size=20),
url_input,
ft.ElevatedButton("Launch URL", on_click=on_launch),
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
)
)
if name == "main":
ft.app(target=main)
'''
with open("main.py", "w") as f:
f.write(main_code)
print("✅ Sample app generated at main.py")
def integrate_package(package_name):
"""Integrate a Flutter package into a Flet project."""
print(f"🚀 Integrating package '{package_name}'")
# Step 1: Set up project and download package
if not setup_project(package_name):
return
# Step 2: Parse Dart code
functions = parse_dart(package_name)
if not functions:
print("⚠️ No public functions found. Wrapper will be minimal.")
# Step 3: Generate Python wrapper
generate_wrapper(package_name, functions)
# Step 4: Generate Dart-Python bridge
generate_dart_bridge(package_name)
# Step 5: Generate sample Flet app
generate_main_app(package_name)
print(f"🎉 Integration complete! Run 'flet run main.py' to test the app.")
def main():
parser = argparse.ArgumentParser(description="Flet Flutter Package Integration Tool")
subparsers = parser.add_subparsers(dest="command")
integrate_parser = subparsers.add_parser("integrate", help="Integrate a Flutter package into a Flet project")
integrate_parser.add_argument("package_name")
args = parser.parse_args()
if args.command == "integrate":
integrate_package(args.package_name)
else:
parser.print_help()
if name == "main":
main()