Flutter 如何使用带有StateNotifier的BLoC模式编辑/更新部分状态?

Flutter 如何使用带有StateNotifier的BLoC模式编辑/更新部分状态?,flutter,dart,Flutter,Dart,我对弗利特比较陌生,对集团模式的国家管理也绝对陌生。我正在处理自己的一个项目,并使用Provider+ChangeNotifier进行状态管理,但我想迁移到使用Riverpod+StateNotifier的BLoC模式,因为这两个新选项似乎都更优越。我遵循了ResoCoder在这里找到的教程: 他使用了一个天气应用程序的示例,该应用程序在文本字段中获取用户指定的城市,并打印给定城市的假温度。因此基本上有一个天气模型,它具有属性城市和温度,还有一个天气存储库类,它获取并返回给定城市的天气数据 本

我对弗利特比较陌生,对集团模式的国家管理也绝对陌生。我正在处理自己的一个项目,并使用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文章中提交了同样的问题,但我希望任何人都能提供任何见解或帮助,以便对已完成的项目进行以下更改:

天气预报器
  • 添加新的WeatherState
    WeatherEdit
  • 将新方法
    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)),
            ),
          ],
        );
      }
    
      ...
    }