【发布时间】:2020-08-09 08:41:07
【问题描述】:
几周以来我一直在用 Flutter 编写应用程序,几天前我开始想知道最好的架构是什么。
先说一点上下文:
- 这是一个使用 Firebase 作为后端的消息传递应用程序;
- 它在很大程度上依赖于美妙的Provider 包来处理整个应用程序的状态
- 计划是拥有多个可以相互交互的功能。
- 我是 Flutter 的新手(主要是 React/ReactNative 背景),它可以解释我下面的奇怪方法。
我一直在体验不同的架构方法,并设法找到一种最终似乎适合我的工作。
由于我将拥有多个功能,可在应用程序的不同位置重复使用,我想按功能(或模块)拆分代码,然后可以在不同的屏幕上独立使用。 文件夹架构如下:
FlutterApp
|
|--> ios/
|--> android/
|--> lib/
|
|--> main.dart
|--> screens/
| |
| |--> logged/
| | |
| | |--> profile.dart
| | |--> settings.dart
| | |--> ...
| |
| |--> notLogged/
| | |
| | |--> home.dart
| | |--> loading.dart
| | |--> ...
|
|--> features/
|
|--> featureA/
| |
| |--> ui/
| | |--> simpleUI.dart
| | |--> complexUI.dart
| |--> provider/
| | |-->featureAProvider.dart
| |--> models/
| |--> featureAModel1.dart
| |--> featureAModel2.dart
| |--> ...
|
|
|--> featureB/
| |
| |--> ui/
| | |--> simpleUI.dart
| | |--> complexUI.dart
| |--> provider/
| | |--> featureBProvider.dart
| |--> models/
| |--> featureBModel1.dart
| |--> featureBModel2.dart
| |--> ...
|
...
理想情况下,每个功能都应遵循以下准则:
- 每个功能都有一个逻辑部分(通常使用 Provider Package);
- 每个功能逻辑部分都可以从另一个功能请求变量(ChangeNotifier 类成员)
- 每个功能都有一个(愚蠢的)UI 部分,可以直接与“逻辑”部分交互(因此可能不那么愚蠢);
- 每个功能都可以将其 UI 部分替换为自定义 UI,但是,自定义 UI 必须自己实现与逻辑部分的交互;
- 如果我以后需要将它们存储在 Firebase 中,每个功能都可以具有代表功能资源的模型
我已经在我的应用程序的一个功能(或 2 个取决于您如何看待它)上尝试了这种方法,即录制/收听语音笔记的能力。 我觉得这很有趣,因为您可以在一个地方录制,但可以在多个地方收听录音:例如,在录音之后或录音发送给您时。
这是我想出的:
-
folder structure of the test
在这种情况下没有
models/文件夹,因为它只是我在其他地方处理的文件 - voiceRecorderNotifier 处理文件(添加/删除)和录音(开始/结束)
- voicePlayerNotifier 需要实例化一个文件(命名构造函数),然后处理音频文件的播放(播放、暂停、停止)。
在代码中,它有点冗长,但可以按预期工作,例如,在屏幕中,我可以像这样请求 voiceRecorder 功能:
class Screen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => VoiceRecorderNotifier(),
child: Column(
children: [
AnUIWidget(),
AnotherUIWidget(),
...,
// The "dumb" feature UI widget from 'features/voiceRecorder/ui/simpleButton.dart' that can be overrided if you follow use the VoiceRecorderNotifier
VoiceRecorderButtonSimple(),
...,
]
)
);
}
}
我也可以让这两个功能(voiceRecorder / voicePlayer)一起工作,如下所示:
class Screen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => VoiceRecorderNotifier(),
child: Column(
children: [
AnUIWidget(),
AnotherUIWidget(),
...,
VoiceRecorderButtonSimple(),
...,
// adding the dependent voicePlayer feature (using the VoiceRecorderNotifier data);
Consumer<VoiceRecorderNotifier>(
builder: (_, _voiceRecorderNotifier, __) {
if (_voiceRecorderNotifier.audioFile != null) {
// We do have audio file, so we put the VoicePlayer feature
return ChangeNotifierProvider<VoicePlayerNotifier>(
create: (_) => VoicePlayerNotifier.withFile(_voiceRecorderNotifier.audioFile),
child: VoicePlayerButtonSimple(),
);
} else {
// We don't have audio file, so no voicePlayer needed
return AnotherUIWidget();
}
}
),
...,
AnotherUIWidget(),
]
)
);
}
}
这是一个新的测试,所以我认为有一些我现在看不到的缺点,但我觉得没有几个优点:
- 更清晰的文件夹结构;
- 一个地方处理与功能相关的“高级”逻辑,易于更新;
- 在应用程序的任何地方都可以轻松添加、移动、删除功能;
- 提供了一个基本 UI 以使该功能按预期工作,例如一个简单的
Text('hi')
但我仍然可以“覆盖”用户界面以显示该功能的特定显示;
- 我可以专注于 UI 和功能的使用,而不是创建大量有状态的组件来在不同的地方复制功能的相同逻辑;
我看到的缺点:
- 功能逻辑是“隐藏的”,每次我想对功能执行特定操作时都需要通过通知程序来记住功能的行为方式;
- 在好的地方实现通知器可能会变得一团糟,如果 UI 小部件可以使用多个功能,那么我将需要多个 FeatureNotifier(即使 Multiprovider 在这种情况下很有用);
最后,问题如下:
- 如果我以后可以继续以这种方式创建功能而不会遇到麻烦,您认为这种方法是否可扩展/推荐?
- 您还发现其他缺点吗?
【问题讨论】:
-
看看这两个系列:resocoder.com/2019/08/27/… 和resocoder.com/2020/03/09/… 另外我建议使用 bloc 库来解决更复杂的状态管理问题
标签: flutter directory-structure flutter-provider