所以,和大多数人一样,我是Bloc的新手,会扑腾、飞镖和绕头。我在谷歌上搜索过,浏览过这里的帖子,但没有找到任何答案。
所以这是关于整体和颤动的导航。以登录为例。所以有一个登录页面,后面有一个区块,在某个时候有人按下按钮登录。
因此,我们可以调用集团中的一个函数来进行验证。我认为这违反了严格的做法,但我看到有人这样做。但是,如果登录成功,您如何导航到下一个屏幕?你不应该在一个集团中航行?
但是,如果登录页面正在使用StreamBuilder更改状态,那么您也无法在生成器中添加导航,是吗?您不能返回导航,而是返回小部件。
initstate是你可以导航的地方,但你能在initstate中有一个流生成器来监听区块中的状态变化吗?
现在这一切都有点令人困惑,但我会坚持下去,因为这是应该的前进之路。。。
谢谢Paul
要想让BLoC成为前进的道路:没有完美的方法来处理状态。每种状态管理架构都能更好地解决某些问题;在决定一个体系结构时,总是会有取舍,意识到它们是很重要的。
一般来说,好的体系结构是实用的:它具有可扩展性和可扩展性,同时只需要最小的开销。由于人们对实用性的看法不同,架构总是涉及意见,所以请谨慎对待以下内容,因为我将阐述我对如何在应用程序中采用BLoC的个人看法。
BLoC是Flutter州管理的一种非常有前途的方法,因为有一个标志性因素:流。它们允许将UI与业务逻辑解耦,并且可以很好地使用Flutter式的方法,在整个小部件子树过时后重新构建它们。因此,自然地,BLoC之间的每一次通信都应该使用流,对吧?
+----+ Stream +------+
| UI | --------> | BLoC |
| | <-------- | |
+----+ Stream +------+
嗯,有点。
需要记住的重要一点是状态管理体系结构是达到目的的一种手段;你不应该只是为了它而做事,而是要保持开放的心态,仔细评估每种选择的利弊。我们将BLoC与UI分离的原因是,BLoC不需要关心UI的结构——它只提供一些简单的流,数据发生的任何事情都是UI的责任。
但是,尽管流已被证明是将信息从BLoC传输到UI的一种极好的方式,但它们在另一个方向上增加了不必要的开销:流被设计为传输连续的数据流(甚至在名称中),但大多数时候,UI只需要在BLoC中触发单个事件。这就是为什么有时你会看到一些Stream<void>
或类似的破解解决方案,只是为了坚持严格的BLoC-y做事方式。
此外,如果我们根据BLoC的流推送新的路由,BLoC基本上会控制UI流——但拥有直接控制UI和业务逻辑的代码正是我们试图阻止的事情!
这就是为什么一些开发人员(包括我)放弃了完全基于流的解决方案,采用了一种从UI在BLoC中触发事件的自定义方式。就我个人而言,我只是使用方法调用(通常返回Future
s)来触发BLoC的事件:
+----+ method calls +------+
| UI | ----------------> | BLoC |
| | <---------------- | |
+----+ Stream, Future +------+
这里,BLoC为"活动"数据返回Stream
s,为方法调用返回Future
s。
让我们看看这对您的示例来说是如何实现的:
- BLoC可以提供用户是否登录的
Stream<bool>
,甚至是Stream<Account>
,其中Account
包含用户的帐户信息 - BLoC还可以提供一个异步
Future<void> signIn(String username, String password)
方法,如果登录成功,该方法将不返回任何信息,否则将抛出错误 - 用户界面可以自行处理输入管理,并在按下登录按钮后触发以下内容:
try {
setState(() => _isLoading = true); // This could display a loading spinner of sorts.
await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
setState(() => _isLoading = false);
// TODO: Display the error on the screen.
}
通过这种方式,您可以很好地分离关注点:
- BLoC实际上只是做它应该做的事情——处理业务逻辑(在本例中,为用户登录)
- UI只关心两件事:
- 显示
Stream
s和 - 通过在BLoC中触发用户操作并根据结果执行UI操作来对用户操作作出反应。²
- 显示
最后,我想指出的是,这只是一种可能的解决方案,它是通过在复杂的应用程序中尝试不同的状态处理方式而随着时间的推移而发展起来的。了解关于国家管理如何运作的不同观点是很重要的,所以我鼓励你深入研究这个话题,也许可以通过观看谷歌I/O的"Pragmatic state management in Flutter"课程。
EDIT:刚刚在Brian Egan的架构示例中发现了这种架构,称为"Simple BLoC"。如果你想了解不同的体系结构,我真的建议你看看repo。
当试图为BLoC操作提供多个参数时,情况会变得更糟——因为这样你就需要定义一个包装类来将其传递给Stream。
²我承认在启动应用程序时会有点难看:你需要某种启动屏幕,它只检查BLoC的流,并根据用户是否登录将用户重定向到适当的屏幕。规则的例外是因为用户执行了一个操作——启动应用程序——但Flutter框架不允许我们直接参与其中(至少据我所知,这并不优雅)。
BlocListener
是您可能需要的小部件。如果状态变为(例如)LoginSuccess
,则块监听器可以调用通常的Navigate.of(context)
。您可以在本页底部附近找到BlocListener
的示例。
另一种选择是将回调传递到事件中。
BlocProvider.of<MyBloc>(context).add(MyEvent(
data: data,
onSuccess: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return HomePage();
}),
);
}));
正如felangel在下一期Github中提到的,我们可以使用BlocListner来实现此目的。
BlocListener(
bloc: BlocProvider.of<DataBloc>(context),
listener: (BuildContext context, DataState state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: BlocBuilder(
bloc: BlocProvider.of<DataBloc>(context),
builder: (BuildContext context, DataState state) {
if (state is Initial) {
return Text('Press the Button');
}
if (state is Loading) {
return CircularProgressIndicator();
}
if (state is Success) {
return Text('Success');
}
if (state is Failure) {
return Text('Failure');
}
},
}
)
首先:如果没有业务逻辑,那么就不需要转到YourBloc类。
但有时一些用户的活动需要在Bloc类中执行一些逻辑,然后Bloc类必须决定下一步要做什么:只需重建小部件或显示对话框,甚至导航到下一条路线。在这种情况下,您必须向UI发送一些状态才能完成操作。然后另一个问题出现了:当Bloc发送State来显示toast时,我应该如何处理小部件?
这就是这个故事的主要问题。
很多答案和文章都建议使用flatter_block。此库具有BlocBuilder和BlocListener。有了这些类,你可以解决一些问题,但不是100%。
在我的案例中,我使用了BlocConsumer,它管理BlocBuilder和BlocListener,并提供了管理状态的出色方法。
来自文件:
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
// return true/false to determine whether or not
// to invoke listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
正如您在BlocConsumer中看到的,您可以过滤状态:您可以轻松地定义状态以重建小部件,并定义状态以显示一些弹出窗口或导航到下一个屏幕。
完全同意@Volodymyr Yatsykiv关于使用BlocConsumer小部件的回答,但只是添加一个在成功登录并在有请求时重建小部件的情况下管理导航的实现示例可能会有所帮助,所以我在下面添加了一个示例。
class LogInBtn extends StatelessWidget {
const LogInBtn({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocConsumer<LogInBloc, LogInStates>(
listener: (context, state){
if(state is SuccessLogInState){
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const HomeScreen()));
}
},
builder: (context, state) {
return state is ProgressLogInState
? const Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 100.0),
child: LinearProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.only(top: 100.0),
child: Container(
height: 40,
decoration: const BoxDecoration(
color: Color(whiteColor),
borderRadius: BorderRadius.all(Radius.circular(5.0))),
child: const Center(
child: Text('Log In',
style: TextStyle(
color: Color(primaryColor),
fontWeight: FontWeight.bold,
fontSize: 20,
fontFamily: 'Laila')),
),
),
);
});
}
}
完成Flutter BLOC
示例
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
BlocOverrides.runZoned(
() => runApp(const App()),
blocObserver: AppBlocObserver(),
);
}
/// Custom [BlocObserver] that observes all bloc and cubit state changes.
class AppBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
if (bloc is Cubit) print(change);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
}
/// {@template app}
/// A [StatelessWidget] that:
/// * uses [bloc](https://pub.dev/packages/bloc) and
/// [flutter_bloc](https://pub.dev/packages/flutter_bloc)
/// to manage the state of a counter and the app theme.
/// {@endtemplate}
class App extends StatelessWidget {
/// {@macro app}
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ThemeCubit(),
child: const AppView(),
);
}
}
/// {@template app_view}
/// A [StatelessWidget] that:
/// * reacts to state changes in the [ThemeCubit]
/// and updates the theme of the [MaterialApp].
/// * renders the [CounterPage].
/// {@endtemplate}
class AppView extends StatelessWidget {
/// {@macro app_view}
const AppView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeCubit, ThemeData>(
builder: (_, theme) {
return MaterialApp(
theme: theme,
home: const CounterPage(),
);
},
);
}
}
/// {@template counter_page}
/// A [StatelessWidget] that:
/// * provides a [CounterBloc] to the [CounterView].
/// {@endtemplate}
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: const CounterView(),
);
}
}
/// {@template counter_view}
/// A [StatelessWidget] that:
/// * demonstrates how to consume and interact with a [CounterBloc].
/// {@endtemplate}
class CounterView extends StatelessWidget {
/// {@macro counter_view}
const CounterView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('$count', style: Theme.of(context).textTheme.headline1);
},
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => context.read<CounterBloc>().add(Increment()),
),
const SizedBox(height: 4),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () => context.read<CounterBloc>().add(Decrement()),
),
const SizedBox(height: 4),
FloatingActionButton(
child: const Icon(Icons.brightness_6),
onPressed: () => context.read<ThemeCubit>().toggleTheme(),
),
],
),
);
}
}
/// Event being processed by [CounterBloc].
abstract class CounterEvent {}
/// Notifies bloc to increment state.
class Increment extends CounterEvent {}
/// Notifies bloc to decrement state.
class Decrement extends CounterEvent {}
/// {@template counter_bloc}
/// A simple [Bloc] that manages an `int` as its state.
/// {@endtemplate}
class CounterBloc extends Bloc<CounterEvent, int> {
/// {@macro counter_bloc}
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}
/// {@template brightness_cubit}
/// A simple [Cubit] that manages the [ThemeData] as its state.
/// {@endtemplate}
class ThemeCubit extends Cubit<ThemeData> {
/// {@macro brightness_cubit}
ThemeCubit() : super(_lightTheme);
static final _lightTheme = ThemeData(
floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: Colors.white,
),
brightness: Brightness.light,
);
static final _darkTheme = ThemeData(
floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: Colors.black,
),
brightness: Brightness.dark,
);
/// Toggles the current brightness between light and dark.
void toggleTheme() {
emit(state.brightness == Brightness.dark ? _lightTheme : _darkTheme);
}
}