Dart/Flutter项目中GUI与CLI程序共享私有模块的实现方法
在Dart/Flutter中实现CLI与GUI共享私有模块的方案
一、采用Monorepo项目结构
直接在同一个代码仓库下拆分三个独立的项目目录,让共享模块、CLI、GUI代码同仓管理,是最方便同步更新的方式:
your_project_root/ ├── shared_core/ # 纯Dart共享模块,存放业务逻辑、模型、工具类 │ ├── lib/ │ ├── test/ │ └── pubspec.yaml ├── cli_tool/ # Dart CLI项目 │ ├── bin/ │ ├── lib/ │ └── pubspec.yaml └── flutter_gui/ # Flutter GUI项目 ├── lib/ ├── ios/ ├── android/ └── pubspec.yaml
二、配置本地路径依赖
在CLI和GUI项目的pubspec.yaml中,直接通过本地路径引用共享模块,无需发布到pub.dev:
CLI项目(cli_tool/pubspec.yaml)
dependencies: shared_core: path: ../shared_core
Flutter GUI项目(flutter_gui/pubspec.yaml)
dependencies: flutter: sdk: flutter shared_core: path: ../shared_core
配置完成后,在CLI项目执行dart pub get,在GUI项目执行flutter pub get,即可关联本地共享模块。
三、代码共享的核心原则
- 保持共享模块的纯Dart特性:
shared_core中只编写与UI、CLI无关的通用代码,比如业务逻辑处理、数据模型定义、工具函数、接口抽象等,不要引入Flutter SDK或CLI特定依赖(如args包),确保两边都能无缝引用。 - 基于接口解耦:在共享模块中定义抽象接口,比如
AuthService、DataProcessor,CLI和GUI项目各自实现适配层(比如CLI用命令行交互处理输入输出,GUI用Flutter组件展示结果),共享模块只负责核心逻辑。 - 避免平台耦合:如果涉及平台相关逻辑(如文件读写、网络请求),在共享模块中定义抽象类,CLI和GUI分别实现对应平台的具体逻辑,保证共享模块的通用性。
四、同步更新与协同演进
- 同仓提交:所有对共享模块的修改,连同CLI/GUI的适配代码一起提交到仓库,确保版本完全同步,不会出现依赖版本不一致的问题。
- 统一测试:在
shared_core中编写单元测试,覆盖核心业务逻辑;CLI和GUI各自编写集成测试,验证自身层的功能正确性,修改共享代码后先跑通单元测试,再验证两边的应用功能。 - 本地实时同步:修改
shared_core代码后,CLI和GUI项目会自动感知到变化(IDE会实时更新),无需额外操作即可使用最新代码,若未生效可重新执行pub get或flutter pub get。
示例场景
比如共享模块shared_core中实现一个TodoManager类:
// shared_core/lib/todo_manager.dart class TodoManager { List<String> _todos = []; void addTodo(String content) { _todos.add(content); } List<String> getTodos() => List.unmodifiable(_todos); }
CLI项目中调用它处理命令行输入:
// cli_tool/bin/main.dart import 'package:shared_core/shared_core.dart'; import 'dart:io'; void main() { final manager = TodoManager(); stdout.write('输入待办事项:'); final content = stdin.readLineSync(); if (content != null) { manager.addTodo(content); print('已添加待办:${manager.getTodos().last}'); } }
Flutter GUI项目中用组件调用展示:
// flutter_gui/lib/main.dart import 'package:flutter/material.dart'; import 'package:shared_core/shared_core.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final _todoManager = TodoManager(); final _controller = TextEditingController(); void _addTodo() { if (_controller.text.isNotEmpty) { setState(() { _todoManager.addTodo(_controller.text); _controller.clear(); }); } } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('待办工具')), body: Column( children: [ TextField(controller: _controller, decoration: const InputDecoration(hintText: '输入待办')), ElevatedButton(onPressed: _addTodo, child: const Text('添加')), Expanded( child: ListView.builder( itemCount: _todoManager.getTodos().length, itemBuilder: (context, index) => ListTile(title: Text(_todoManager.getTodos()[index])), ), ) ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Fractale
相关产品推荐
相关产品推荐

