gpt4 book ai didi

Flutter Bloc listener and builder don't react to state changes(颤动区块监听器和构建器不会对状态更改做出反应)

转载 作者:bug小助手 更新时间:2023-10-25 17:51:06 26 4
gpt4 key购买 nike



I need to implement a feature in my application that gives the possibility to users to enter a password and switch servers (toggle staging server if they were using production server, or toggle production server if they were using staging server).

我需要在我的应用程序中实现一项功能,允许用户输入密码并切换服务器(如果他们使用的是生产服务器,则切换中间服务器;如果他们使用的是中间服务器,则切换生产服务器)。


The theme of my app needs to be updated as well when the environment server changes to let the user know they've changed servers successfully.

当环境服务器改变时,我的应用程序的主题也需要更新,以让用户知道他们已经成功地更改了服务器。


I'm using flutter_bloc package and cubits. The environment gets changed successfully, but I can't seem to get the theme of my app to update in my main function.

我用的是FIFTTER_BLOC套装和肘部。环境改变成功,但我似乎无法在我的主功能中更新我的应用程序的主题。


Here is my Environment Cubit :

这是我的环境丘比特:



class EnvironmentCubit extends LoggedCubit<EnvironmentState> {
EnvironmentCubit() : super(EnvironmentState(environment: Constants.environment));

void toggleServer({required String password}) {
if (password != Constants.switchServerPassword) {
emit(state.copyWith(success: false, error: 'Password is incorrect.'));
return;
}
_toggleServer();
}

void _toggleServer() {
final newEnvironment =
state.environment == Environment.production
? Environment.staging
: Environment.production;
emit(EnvironmentState(success: true, error: null, environment: newEnvironment));
print("Environment has changed to " + state.environment.name); // TRIGGERS ✔️
}
}

class EnvironmentState {
final bool success;
final String? error;
final Environment environment;

EnvironmentState({this.success = false, this.error, required this.environment});
}


Here is my AppTheme class :

下面是我的AppTheme类:



class AppColors {
static const Color colorPrimary = Colors.green;
static const Color colorPrimaryDev = Colors.blue;
static const Color colorPrimaryStaging = Colors.red;
}

class AppTheme {

static ThemeData theme =
_generateTheme(Constants.environment);

static ThemeData _generateTheme(Environment environment) {
final primaryColor = PrimaryColor.fromEnvironment(environment);

return ThemeData(
appBarTheme: AppBarTheme(backgroundColor: primaryColor.color),
primaryColor: primaryColor.color,
// Some other styles ...
);
}

static ThemeData getTheme(Environment environment) {
print("getTheme is called."); // ❌ DOES NOT TRIGGER WHEN STATE CHANGES
return _generateTheme(environment);
}
}

enum PrimaryColor {
development(AppColors.colorPrimaryDev),
staging(AppColors.colorPrimaryStaging),
production(AppColors.colorPrimary);

final Color color;
const PrimaryColor(this.color);

static PrimaryColor fromEnvironment(Environment environment) {
switch (environment) {
case Environment.development:
return PrimaryColor.development;
case Environment.staging:
return PrimaryColor.staging;
case Environment.production:
return PrimaryColor.production;
default:
return PrimaryColor.development;
}
}
}


Here is my main.dart file:

以下是我的main.dart文件:



void main() async {
WidgetsFlutterBinding.ensureInitialized();
await configureDependencies();
Bloc.observer = locator.get<Logger>().blocObserver;

runApp(
MultiBlocProvider(
providers: [
BlocProvider<EnvironmentCubit>(
create: (context) => EnvironmentCubit(),
),
],
child: const MyApp(),
),
);
}

class MyApp extends StatelessWidget {
const MyApp ({super.key});

@override
Widget build(BuildContext context) {
return BlocConsumer<EnvironmentCubit, EnvironmentState>(
listener: (context, state) {
print("Listener is called. Environment is " + state.environment.name); // ❌ DOES NOT TRIGGER
},
builder: (context, state) {
final theme = AppTheme.getTheme(state.environment);
print("Widget is (re-)building."); // ❌ DOES NOT TRIGGER AGAIN AFTER STATE CHANGES
return MaterialApp.router(
title: "My App",
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
routerConfig: locator<RouterConfig<Object>>(),
theme: theme,
);
},
);
}

// The route configuration.
static RouterConfig<Object> createRouter() => GoRouter(
initialLocation: Routes.splashScreen,
routes: <RouteBase>[
GoRoute(
path: Routes.home,
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
},
),
GoRoute(
path: Routes.login,
builder: (BuildContext context, GoRouterState state) {
return const LoginPage();
},
),
// Other routes...
],
);
}


Here's the Login page where the user can switch servers:

以下是用户可以在其中切换服务器的登录页面:



class LoginPage extends StatelessWidget {
const LoginPage ({super.key});

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EnvironmentCubit(),
child: LoginPageContent(),
);
}
}

class LoginPageContent extends StatelessWidget {
final form = FormGroup({
'password': FormControl<String>(validators: [Validators.required]),
});

LoginPageContent({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<EnvironmentCubit, EnvironmentState>(
builder: (context, state) {
return Padding(
padding: EdgeInsets.all(16),
child: ReactiveForm(
formGroup: form,
child: Column(
children: [
Text("You are on server: " + state.environment.name),
ReactiveTextField<String>(
formControlName: 'password',
decoration: InputDecoration(labelText: 'Enter password:'),
),
ElevatedButton(
onPressed: () {
_validatePassword(context);
},
child: Text('Confirm'),
),
],
),
),
);
},
listener: (context, state) {
print("Success. State has changed."); // TRIGGERS ✔️
}
},
),
);
}

void _validatePassword(BuildContext context) {
if (form.valid) {
print("Form is valid."); // TRIGGERS ✔️
final password = form.control('password').value;
context.read<EnvironmentCubit>().toggleServer(password: password);
}
}
}


On my HomePage, I can display the name of the environment and notice the state changes. But in my main function, the BlocProvider and BlocConsumer don't seem to react to any state changes, and the theme doesn't change.

在我的主页上,我可以显示环境的名称并注意到状态的变化。但在我的主要函数中,BlocProvider和BlocConsumer似乎不会对任何状态更改做出反应,主题也不会改变。


I think the problem might come from the fact I'm using EnvironmentCubit on both the Login page (where state changes effectively) and in my main function (where it doesn't work).

我认为问题可能来自于我在登录页面(状态更改有效)和我的Main函数(它不工作)上都使用了Environment Cubit这一事实。


How can I solve this?

我怎么才能解决这个问题?


更多回答
优秀答案推荐

Great you found a workaround.

很好,你找到了一个解决方案。


So first off: An easy way to work with blocs/cubit is to use it with a dependency Injection tool. Usually this is GetIt. If you had registered your EnvironmentCubit as a singleton in GetIt. Whenever you use getIt to call an instance, it will get the same bloc instance. And your consumers would respond to the same cubit/bloc instance all the time unless you use create a local instance.

因此,首先:处理块/CUBIT的一个简单方法是将其与依赖项注入工具一起使用。通常情况下,这是GetIt。如果您已在GetIt中将您的Environment Cubit注册为Singleton。无论何时使用getIt调用一个实例,它都会得到相同的块实例。除非您使用创建本地实例,否则您的消费者将一直响应同一个cubit/block实例。


Additionally, I actually thought your states didn't extend Equatable. and I was right. Why?
One more reason states may not rebuild in Bloc/Cubit, is the Bloc Consumer/Listener thinks the new Environment States being emitted are the equal to the Old Environment states present. Equatable makes the listener know the states aren't the same. So, your EnvironmentState should extend Equatable, where you have to override it's props field.
This should get your blocs rebuilding when the state changes.

另外,我还以为你们的州并不是平分秋色的。我是对的。为什么?BLOC/CUBIT中状态可能不会重建的另一个原因是,BLOC消费者/监听者认为正在发出的新环境状态与现有的旧环境状态相同。Equatable让听众知道状态不同。因此,您的Environment State应该扩展Equatable,在那里您必须重写它的道具字段。当状态改变时,这应该会让你的区块重建。


What does Equatable do?
"Equatable is used to simplify the process of comparing instances of the same object without the need for boilerplate codes of overriding “==” and hashCodes."
like below

Equatable是做什么的?Equatable用于简化比较同一对象的实例的过程,而不需要覆盖“==”和hashCodes的样板代码。如下所示


    class EnvironmentState extends Equatable {
final bool success;
final String? error;
final Environment environment;

EnvironmentState(
{this.success = false, this.error, required this.environment});

@override
List<Object?> get props => [
success,
error,
environment,
];
}

You can have a look at This Get It introduction
and this Equatable example

你可以看看这个Get It简介和这个等同的例子



I'm still a newbie and just realised I couldn't use two distinct instances of EnvironmentCubit, since they both have their own state and absolutely do not synchronise.

我还是个新手,刚刚意识到我不能使用环境Cubit的两个不同的实例,因为它们都有自己的状态,并且绝对不同步。


I removed the BlocProvider from my LoginPage and declare the cubit in the build method of my widget:

我从我的LoginPage中删除了BlocProvider,并在我的小部件的Build方法中声明了cubit:


final environmentCubit = BlocProvider.of<EnvironmentCubit>(context);


Then I edited the _validatePassword() function to add the cubit as a parameter:

然后,我编辑了_validatePassword()函数,将cubit添加为参数:


void _validatePassword(BuildContext context, EnvironmentCubit environmentCubit) {
if (form.valid) {
final password = form.control('password').value;
environmentCubit.toggleServer(password: password);
}
}

...and in my ElevatedButton's onPressed() function, I called the _validatePassword() function like this:

...在我的ElevatedButton的onPressed()函数中,我调用了_validatePassword()函数,如下所示:


_validatePassword(context, environmentCubit);

Now it works perfectly.

现在,它工作得很完美。


更多回答

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com