Flutter 如何使用带有StateNotifier的BLoC模式编辑/更新部分状态?
我对弗利特比较陌生,对集团模式的国家管理也绝对陌生。我正在处理自己的一个项目,并使用Provider+ChangeNotifier进行状态管理,但我想迁移到使用Riverpod+StateNotifier的BLoC模式,因为这两个新选项似乎都更优越。我遵循了ResoCoder在这里找到的教程: 他使用了一个天气应用程序的示例,该应用程序在Flutter 如何使用带有StateNotifier的BLoC模式编辑/更新部分状态?,flutter,dart,Flutter,Dart,我对弗利特比较陌生,对集团模式的国家管理也绝对陌生。我正在处理自己的一个项目,并使用Provider+ChangeNotifier进行状态管理,但我想迁移到使用Riverpod+StateNotifier的BLoC模式,因为这两个新选项似乎都更优越。我遵循了ResoCoder在这里找到的教程: 他使用了一个天气应用程序的示例,该应用程序在文本字段中获取用户指定的城市,并打印给定城市的假温度。因此基本上有一个天气模型,它具有属性城市和温度,还有一个天气存储库类,它获取并返回给定城市的天气数据 本
文本字段中获取用户指定的城市,并打印给定城市的假温度。因此基本上有一个天气
模型,它具有属性城市
和温度
,还有一个天气存储库
类,它获取并返回给定城市的天气数据
本质上,为了处理状态,他首先创建一组类型为WeatherState
的类,对应用程序可能处于的不同状态进行建模。这些类看起来像:
abstract class WeatherState {...}
class WeatherInitial extends WeatherState {...}
class WeatherLoading extends WeatherState {...}
class WeatherLoaded extends WeatherState {
//the below is an object that holds the "actual" application data to be passed to this WeatherState state in success/loaded situations.
final Weather weatherObject;
WeatherLoaded(this.weatherObject);
}
然后,根据应用程序的逻辑状态,通过简单地为状态分配不同的WeatherState
s来实现StateNotifier,如下所示:
class WeatherNotifer extends StateNotifier<WeatherState>{
// WeatherRepository holds the application logic for downloading weather data from the internet
final WeatherRepository _weatherRepository;
WeatherNotifier(this._weatherRepository) : super(WeatherInitial());
Future<void> getWeather(String cityName) async {
try {
state = WeatherLoading();
final weather = await _weatherRepository.fetchWeather(cityName);
state = WeatherLoaded(weather);
} on NetworkException {
state = WeatherError("Couldn't fetch weather. Is the device online?");
}
}
}
类WeatherNotifier扩展StateNotifier{
//WeatherRepository包含从internet下载天气数据的应用程序逻辑
最终天气储存库(WeatherRepository);;
WeatherNotifier(this.\u weatherRepository):super(WeatherInitial());
Future getWeather(字符串cityName)异步{
试一试{
状态=天气负荷();
final weather=wait_weatherRepository.fetchWeather(cityName);
状态=天气负载(天气);
}论网络例外{
state=WeatherError(“无法获取天气。设备是否联机?”);
}
}
}
上述代码非常适用于此应用程序,因为应用程序数据的状态每次都会被过度写入fetchWeather
的结果,并且每次通过文本字段提交新城市时,整个状态循环都会运行
但是,如果我想编辑已加载状态的一部分,我将如何处理这种情况?我知道状态是不可变的,我需要用一个包含更改值的新状态覆盖状态,但是我在访问和编辑这部分状态的逻辑上遇到了问题,因为StateNotifier
中的“状态”是WeatherState
类型,而不是Weather
类型,所以它没有直接保存我想要编辑的天气对象。它保存一个WeatherState对象,仅当当前WeatherState恰好是WeatherLoaded时才保存一个WeatherState对象
例如:假设我们有一个“编辑天气”按钮,它调用一个editWeather(double newTemperature)
方法,在获取特定城市的天气后,该方法为用户提供编辑返回温度值的选项,从而更新状态。这种方法在哪里实现?如何编辑类型为WeatherState的状态的Weather属性,根据当前状态,该状态可能有天气对象,也可能没有天气对象
我在链接的ResoCoder文章中提交了同样的问题,但我希望任何人都能提供任何见解或帮助,以便对已完成的项目进行以下更改:
天气预报器
添加新的WeatherStateWeatherEdit
将新方法startweatherdit()
和finishWeatherEdit()
添加到WeatherNotifier
最后,添加“编辑天气”按钮
class\u WeatherSearchPageState扩展状态{
...
列构建ColumnWithData(天气){
返回列(
mainAxisAlignment:mainAxisAlignment.space,
儿童:[
正文(
...
),
正文(
...
),
CityInputField(),
升起的按钮(
按下:()=>
context.read(weatherNotifierProvider).startWeatherEdit(),
子项:常量文本('Edit Weather',样式:TextStyle(fontSize:20)),
),
],
);
}
...
}
class WeatherEdit extends WeatherState {
final Weather weather;
const WeatherEdit(this.weather);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is WeatherEdit && o.weather == weather;
}
@override
int get hashCode => weather.hashCode;
}
class WeatherNotifier extends StateNotifier<WeatherState> {
...
Future<void> getWeather(String cityName) async {
...
}
void startWeatherEdit() {
state = WeatherEdit((state as WeatherLoaded).weather);
}
void finishWeatherEdit(double newTemperature) {
Weather newWeather = Weather(
cityName: (state as WeatherEdit).weather.cityName,
temperatureCelsius: newTemperature);
state = WeatherLoaded(newWeather);
}
}
class _WeatherSearchPageState extends State<WeatherSearchPage> {
@override
Widget build(BuildContext context) {
...
child: Consumer(
builder: (context, watch, child) {
final state = watch(weatherNotifierProvider.state);
if (state is WeatherInitial) {
return buildInitialInput();
} else if (state is WeatherLoading) {
return buildLoading();
} else if (state is WeatherLoaded) {
return buildColumnWithData(state.weather);
} else if (state is WeatherEdit) {
return buildWeatherEditUI(state.weather);
} else {
// (state is WeatherError)
return buildInitialInput();
}
},
),
...
}
...
}
class _WeatherSearchPageState extends State<WeatherSearchPage> {
...
Column buildWeatherEditUI(Weather weather) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
weather.cityName,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w700,
),
),
Text(
// Display the temperature with 1 decimal place
"${weather.temperatureCelsius.toStringAsFixed(1)} °C",
style: TextStyle(fontSize: 80),
),
WeatherEditField(),
],
);
}
}
// helper widget
class WeatherEditField extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: TextField(
onSubmitted: (value) => submitWeatherEdit(context, value),
textInputAction: TextInputAction.done,
decoration: InputDecoration(
hintText: "Enter a new temperature",
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
suffixIcon: Icon(Icons.done),
),
),
);
}
void submitWeatherEdit(BuildContext context, String newTemperature) {
context
.read(weatherNotifierProvider)
.finishWeatherEdit(double.parse(newTemperature));
}
}
class _WeatherSearchPageState extends State<WeatherSearchPage> {
...
Column buildColumnWithData(Weather weather) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
...
),
Text(
...
),
CityInputField(),
RaisedButton(
onPressed: () =>
context.read(weatherNotifierProvider).startWeatherEdit(),
child: const Text('Edit Weather', style: TextStyle(fontSize: 20)),
),
],
);
}
...
}