[英]Flutter app architecture using "modules" with provider package
几周以来,我一直在 Flutter 中编写应用程序,几天前我开始想知道最好的架构是什么。
先说一点上下文:
我一直在体验不同的架构方法,并设法找到一种最终似乎适合我的工作。
由于我将拥有多个功能,可在应用程序的不同位置重用,我想将代码按功能(或模块)拆分,然后可以在不同的屏幕上独立使用。 文件夹架构如下:
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
| |--> ...
|
...
理想情况下,每个功能都遵循以下准则:
我已经用我的应用程序的一个功能(或 2 个取决于你如何看待它)尝试了这种方法,即录制/收听语音笔记的能力。 我发现这很有趣,因为您可以在一个地方录制,但可以在多个地方收听录音:例如,在录音之后或录音发送给您时。
这是我想出的:
models/
文件夹,因为它只是我在其他地方处理的文件在代码中,它有点冗长,但按预期工作,例如,在屏幕中,我可以请求 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(),
]
)
);
}
}
这是一个新的测试,所以我认为有一些我现在看不到的缺点,但我觉得有几个优点:
Text('hi')
但我仍然可以“覆盖” UI 以特定显示该功能;
我看到的缺点:
最后,以下是问题:
Provider 是一个很棒的工具,可以帮助您访问整个应用程序中的所有数据。 我没有看到有关它当前如何在您的应用上实现的任何问题。
在您正在寻找的点上,例如处理逻辑和更新 UI,您可能需要研究 BloC 模式。 有了这个,您可以通过 Stream 处理 UI 更新,并且可以在 StreamBuilder 上更新 UI。
此示例演示使用 BloC 模式更新 Flutter 应用程序上的 UI。 这是可以处理所有逻辑的部分。 Timer
用于模拟 HTTP 响应的等待时间。
class Bloc {
/// UI updates can be handled in Bloc
final _repository = Repository();
final _progressIndicator = StreamController<bool>.broadcast();
final _updatedNumber = StreamController<String>.broadcast();
/// StreamBuilder listens to [showProgress] to update UI to show/hide the LinearProgressBar
Stream<bool> get showProgress => _progressIndicator.stream;
/// StreamBuilder listens to [updatedNumber] to update UI
Stream<String> get updatedNumber => _updatedNumber.stream;
updateShowProgress(bool showProgress) {
_progressIndicator.sink.add(showProgress);
}
/// Updates the List<UserThreads> Stream
fetchUpdatedNumber(String number) async {
bloc.updateShowProgress(true); // Show ProgressBar
/// Timer mocks an instance where we're waiting for
/// a response from the HTTP request
Timer(Duration(seconds: 2), () async {
// delay for 4 seconds to display LinearProgressBar
var updatedNumber = await _repository.fetchUpdatedNumber(number);
_updatedNumber.sink.add(updatedNumber); // Update Stream
bloc.updateShowProgress(false); // Hide ProgressBar
});
}
dispose() {
_updatedNumber.close();
}
disposeProgressIndicator() {
_progressIndicator.close();
}
}
/// this enables Bloc to be globally accessible
final bloc = Bloc();
/// Class where we can keep Repositories that can be accessed in Bloc class
class Repository {
final provider = Provider();
Future<String> fetchUpdatedNumber(String number) =>
provider.updateNumber(number);
}
/// Class where all backend tasks can be handled
class Provider {
Future<String> updateNumber(String number) async {
/// HTTP requests can be done here
return number;
}
}
这是我们的主要应用程序。 注意我们不再需要调用setState()
来刷新 UI。 UI 更新依赖于对它们设置的 StreamBuilder。 每次使用StreamController.broadcast.sink.add(Object)
对 Stream 进行更新时,StreamBuilder 都会再次重建以更新 UI。 StreamBuilder 还用于显示/隐藏 LinearProgressBar。
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BloC Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
bloc.fetchUpdatedNumber('${++_counter}');
// setState(() {
// _counter++;
// });
}
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
stream: bloc.showProgress,
builder: (BuildContext context, AsyncSnapshot<bool> progressBarData) {
/// To display/hide LinearProgressBar
/// call bloc.updateShowProgress(bool)
var showProgress = false;
if (progressBarData.hasData && progressBarData.data != null)
showProgress = progressBarData.data!;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
bottom: showProgress
? PreferredSize(
preferredSize: Size(double.infinity, 4.0),
child: LinearProgressIndicator())
: null,
),
body: StreamBuilder<String>(
stream: bloc.updatedNumber,
builder: (BuildContext context,
AsyncSnapshot<String> numberSnapshot) {
var number = '0';
if (numberSnapshot.hasData && numberSnapshot.data != null)
number = numberSnapshot.data!;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$number',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
});
}
}
演示
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.