内容简介:Flutter 提供了一种现代的响应式框架,丰富的组件集和工具,但是还没有如同 Android 中应用架构指南一样的东西。的确,没有任何终极架构方案能满足所有需求,但我们面对的事实是,我们正在开发的大多数移动应用至少具有以下的某些功能:考虑到这一点,我创建了一个示例应用,使用三种不同的架构方法解决完全相同的问题。
Flutter 提供了一种现代的响应式框架,丰富的组件集和工具,但是还没有如同 Android 中应用架构指南一样的东西。
的确,没有任何终极架构方案能满足所有需求,但我们面对的事实是,我们正在开发的大多数移动应用至少具有以下的某些功能:
- 从网络请求数据/向网络上传数据。
- 遍历,转换,准备数据并呈现给用户。
- 向数据库发送数据/从数据库获取数据。
考虑到这一点,我创建了一个示例应用,使用三种不同的架构方法解决完全相同的问题。
在屏幕中央向用户显示“加载用户数据”按钮。当用户单击该按钮时,将异步加载数据,并使用加载指示器替换该按钮。数据加载完成后,加载指示器将替换为数据。
让我们开始吧。
数据
为了简单起见,我创建了类 Repository
,其中包含模拟异步网络调用的方法 getUser()
,并返回带有硬编码值的 Future<User>
对象。 如果您不熟悉 Dart 中的 Futures 和异步编程,可以通过这个教程或阅读文档来了解更多相关信息。
class Repository { Future<User> getUser() async { await Future.delayed(Duration(seconds: 2)); return User(name: 'John', surname: 'Smith'); } } 复制代码
class User { User({ @required this.name, @required this.surname, }); final String name; final String surname; } 复制代码
Vanilla
让我们按照大多数开发人员阅读 Flutter 官方文档后的方式构建应用。
使用 Navigator
导航到 VanillaScreen
页面。
由于组件的状态可能会在其生命周期中多次更改,因此我们应该继承 StatefulWidget
。实现有状态组件还需要具有类 State
。类 _VanillaScreenState
中的字段 bool _isLoading
和 User _user
表示组件的状态。在调用 build(BuildContext context)
方法之前,这两个字段都已初始化。 创建组件状态对象后,将调用 build(BuildContext context)
方法来构建 UI。关于如何构建表示组件当前状态的所有决策都在 UI 声明代码中做出。
body: SafeArea( child: _isLoading ? _buildLoading() : _buildBody(), ) 复制代码
当用户单击“加载用户详细信息”按钮时,为了显示进度指示器,我们执行以下操作。
setState(() { _isLoading = true; }); 复制代码
调用 setState()
会通知框架该对象的内部状态已经发生改变,并有可能影响此子树中的用户界面,这会导致框架为此 State 对象安排构建。
这意味着在调用 setState()
方法后,框架再次调用 build(BuildContext context)
方法, 并重建整个组件树 。由于 _isLoading
现在设置为 true
,因此调用 _buildLoading()
而不是 _buildBody()
,并在屏幕上显示加载指示器。与当我们处理来自 getUser()
的回调并调用 setState()
来重新分配 _isLoading
和 _user
字段的情况相同。
widget._repository.getUser().then((user) { setState(() { _user = user; _isLoading = false; }); }); 复制代码
优点
- 学习简单,易于理解。
- 不需要第三方库。
缺点
- 组件的状态的每次改变都会重建整个组件树。
- 它打破了单一责任原则。组件不仅负责构建 UI,还负责数据加载,业务逻辑和状态管理。
- 关于如何表示当前状态的决策是在 UI 声明代码中做出的。如果我们的状态复杂一些,代码可读性会降低。
Scoped Model
Scoped Model是第三方包,未包含在 Flutter 框架中。 这是 Scoped Model 开发人员的描述:
一组实用程序,允许您轻松地将数据模型从父组件传递到其后代。此外,它还会在模型更新时重建使用该模型的所有子项。该库最初是从 Fuchsia 代码库中提取的。
让我们使用 Scoped Model 构建相同的页面。首先,我们需要通过 pubspec.yaml
在 dependencies
下添加 scoped_model
依赖项来安装 Scoped Model 包。
scoped_model: ^1.0.1 复制代码
让我们看一下 UserModelScreen
组件,并将其与之前未使用 Scoped Model 构建的示例进行比较。由于我们想让我们的模型可用于所有组件的后代,我们应该使用通用的 ScopedModel
包装它并提供组件和模型。
class UserModelScreen extends StatefulWidget { UserModelScreen(this._repository); final Repository _repository; @override State<StatefulWidget> createState() => _UserModelScreenState(); } class _UserModelScreenState extends State<UserModelScreen> { UserModel _userModel; @override void initState() { _userModel = UserModel(widget._repository); super.initState(); } @override Widget build(BuildContext context) { return ScopedModel( model: _userModel, child: Scaffold( appBar: AppBar( title: const Text('Scoped model'), ), body: SafeArea( child: ScopedModelDescendant<UserModel>( builder: (context, child, model) { if (model.isLoading) { return _buildLoading(); } else { if (model.user != null) { return _buildContent(model); } else { return _buildInit(model); } } }, ), ), ), ); } Widget _buildInit(UserModel userModel) { return Center( child: RaisedButton( child: const Text('Load user data'), onPressed: () { userModel.loadUserData(); }, ), ); } Widget _buildContent(UserModel userModel) { return Center( child: Text('Hello ${userModel.user.name} ${userModel.user.surname}'), ); } Widget _buildLoading() { return const Center( child: CircularProgressIndicator(), ); } } 复制代码
在前面的示例中,当组件的的状态发生更改时,重建了整个组件树。但我们真的需要重建整个页面吗?例如,AppBar 根本不应该改变,因此重建它没有意义。理想情况下,我们应该只重建那些更新的组件。Scoped Model 可以帮助我们解决这个问题。
ScopedModelDescendant<UserModel>
组件用于在组件树中查找 UserModel
。只要 UserModel
通知发生了更改,它就会自动重建。
另一个改进是 UserModelScreen
不再负责状态管理和业务逻辑。
我们来看看 UserModel
代码。
class UserModel extends Model { UserModel(this._repository); final Repository _repository; bool _isLoading = false; User _user; User get user => _user; bool get isLoading => _isLoading; void loadUserData() { _isLoading = true; notifyListeners(); _repository.getUser().then((user) { _user = user; _isLoading = false; notifyListeners(); }); } static UserModel of(BuildContext context) => ScopedModel.of<UserModel>(context); } 复制代码
现在 UserModel
保存并管理状态。为了通知监听器(并重建后代)发生了更改,应调用 notifyListeners()
方法。
优点
- 业务逻辑,状态管理和 UI 代码分离。
- 简单易学。
缺点
notifyListeners()
BLoC
BLoC( B usiness L ogic C omponents)是 Google 开发人员推荐的模式。它利用流功能来管理和广播状态更改。
对于 Android 开发人员:您可以将 Bloc
对象视为 ViewModel
,将 StreamController
视为 LiveData
。这将使以下代码非常简单,因为您已经熟悉了这些概念。
class UserBloc { UserBloc(this._repository); final Repository _repository; final _userStreamController = StreamController<UserState>(); Stream<UserState> get user => _userStreamController.stream; void loadUserData() { _userStreamController.sink.add(UserState._userLoading()); _repository.getUser().then((user) { _userStreamController.sink.add(UserState._userData(user)); }); } void dispose() { _userStreamController.close(); } } class UserState { UserState(); factory UserState._userData(User user) = UserDataState; factory UserState._userLoading() = UserLoadingState; } class UserInitState extends UserState {} class UserLoadingState extends UserState {} class UserDataState extends UserState { UserDataState(this.user); final User user; } 复制代码
当状态改变时,不需要额外的方法调用来通知订阅者。
我创建了 3 个类来表示页面的可能状态:
UserInitState UserLoadingState UserDataState
以这种方式广播状态更改允许我们摆脱 UI 声明代码中的所有逻辑。在使用 Scoped Model 的示例中,我们仍在检查 UI 声明代码中的 _isLoading
是否为 true
,以决定我们应该呈现哪个组件。在 BLoC 的示例中,我们正在广播页面的状态, UserBlocScreen
组件的唯一责任是呈现此状态的 UI。
class UserBlocScreen extends StatefulWidget { UserBlocScreen(this._repository); final Repository _repository; @override State<StatefulWidget> createState() => _UserBlocScreenState(); } class _UserBlocScreenState extends State<UserBlocScreen> { UserBloc _userBloc; @override void initState() { _userBloc = UserBloc(widget._repository); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Bloc'), ), body: SafeArea( child: StreamBuilder<UserState>( stream: _userBloc.user, initialData: UserInitState(), builder: (context, snapshot) { if (snapshot.data is UserInitState) { return _buildInit(); } if (snapshot.data is UserDataState) { UserDataState state = snapshot.data; return _buildContent(state.user); } if (snapshot.data is UserLoadingState) { return _buildLoading(); } }, ), ), ); } Widget _buildInit() { return Center( child: RaisedButton( child: const Text('Load user data'), onPressed: () { _userBloc.loadUserData(); }, ), ); } Widget _buildContent(User user) { return Center( child: Text('Hello ${user.name} ${user.surname}'), ); } Widget _buildLoading() { return const Center( child: CircularProgressIndicator(), ); } @override void dispose() { _userBloc.dispose(); super.dispose(); } } 复制代码
与前面的示例相比, UserBlocScreen
代码变得更加简单。我们使用 StreamBuilder
监听状态更改。 StreamBuilder
是一个 StatefulWidget
,它基于与 Stream
交互的最新快照来构建自身。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 架构简洁之道:从阿里开源应用架构 COLA 说起
- 『互联网架构』软件架构-掌握dubbo常规应用(上)(40)
- 『互联网架构』软件架构-掌握dubbo常规应用(下)(41)
- 『互联网架构』软件架构-企业级dubbo应用(上)(42)
- 『互联网架构』软件架构-企业级dubbo应用(下)(43)
- [译] 在 Kubernetes 之上架构应用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python源码剖析
陈儒 / 电子工业出版社 / 2008-6 / 69.80元
作为主流的动态语言,Python不仅简单易学、移植性好,而且拥有强大丰富的库的支持。此外,Python强大的可扩展性,让开发人员既可以非常容易地利用C/C++编写Python的扩展模块,还能将Python嵌入到C/C++程序中,为自己的系统添加动态扩展和动态编程的能力。. 为了更好地利用Python语言,无论是使用Python语言本身,还是将Python与C/C++交互使用,深刻理解Pyth......一起来看看 《Python源码剖析》 这本书的介绍吧!
XML 在线格式化
在线 XML 格式化压缩工具
HEX CMYK 转换工具
HEX CMYK 互转工具