[英]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.