首页 > 解决方案 > 在 Flutter StateNotifier + Riverpod 架构中初始化未来数据

问题描述

所以我在这篇文章中实现了 ResoCoder 在 YouTube 上展示的架构:https ://resocoder.com/2020/12/11/flutter-statenotifier-riverpod-tutorial-immutable-state-management/#t-1607415476843 。

我在理解什么是按照他的方案初始化未来数据的最佳方法时遇到了一些问题。

假设我有一个虚拟的 Repository 类:

abstract class WeatherRepository {
  Future<Weather> fetchWeather(String cityName);
}

class FakeWeatherRepository implements WeatherRepository {
  double cachedTempCelsius;

  @override
  Future<Weather> fetchWeather(String cityName) {
    // Simulate network delay
    return Future.delayed(
      Duration(seconds: 1),
      () {
        final random = Random();

        // Simulate some network exception
        if (random.nextBool()) {
          throw NetworkException();
        }

        // Since we're inside a fake repository, we need to cache the temperature
        // in order to have the same one returned in for the detailed weather
        cachedTempCelsius = 20 + random.nextInt(15) + random.nextDouble();

        // Return "fetched" weather
        return Weather(
          cityName: cityName,
          // Temperature between 20 and 35.99
          temperatureCelsius: cachedTempCelsius,
        );
      },
    );
  }
}

class NetworkException implements Exception {}

天气状态类:

abstract class WeatherState {
  const WeatherState();
}

class WeatherInitial extends WeatherState {
  const WeatherInitial();
}

class WeatherLoading extends WeatherState {
  const WeatherLoading();
}

class WeatherLoaded extends WeatherState {
  final Weather weather;
  const WeatherLoaded(this.weather);
}

class WeatherError extends WeatherState {
  final String message;
  const WeatherError(this.message);
}

天气通知器:

class WeatherNotifier extends StateNotifier<WeatherState> {
  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?");
    }
  }
}

两家供应商:

final weatherRepositoryProvider = Provider<WeatherRepository>(
  (ref) => FakeWeatherRepository(),
);

final weatherNotifierProvider = StateNotifierProvider(
  (ref) => WeatherNotifier(ref.watch(weatherRepositoryProvider)),
);

和 WeatherSearchPage (UI):

...
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 {
      // (state is WeatherError)
      return buildInitialInput();
    }
  },
),
...

在哪里

Widget buildInitialInput() {
    return Center(
      child: CityInputField(), // builds a textfield to fetch the weather of some city
    );
}

  Widget buildLoading() {
    return Center(
      child: CircularProgressIndicator(),
    );
  }
Column buildColumnWithData(Weather weather) {
    return Column(
       ...
       //shows data
       ...
    );
  }

如果我需要在创建页面时获取默认城市天气,我应该按照这个逻辑在哪里调用它?

我尝试将 WeatherSearchPage (UI) 转换为有状态小部件并像这样调用 initState

@override
  void initState() {
    context.read(weatherNotifierProvider).getWeather("Siena");
    super.initState();
  }

它有效,但看起来不是很干净,并且没有利用小部件的 InitialState。有什么建议么?

谢谢!

标签: firebaseflutterclean-architectureriverpod

解决方案


首先,在这里使用 StateNotifier 没有多大意义。这并没有错,但我想我可以向您展示一种更简单的方法来做到这一点,它真正利用了 Riverpod 所提供的优势。

您可以使用FutureProvider并使用AsyncValue状态包装组件的内容。

class FakeWeatherRepository implements WeatherRepository {
  double cachedTempCelsius;

  static final provider = Provider<FakeWeatherRepository>((_) => FakeWeatherRepository());

  @override
  Future<Weather> fetchWeather(String cityName) async {
    // Get weather
  }
}

final weatherProvider = FutureProvider.family<Weather, String>((ref, city) {
  final repo = ref.watch(FakeWeatherRepository.provider);
  return repo.fetchWeather(city);
});

class WeatherWidget extends ConsumerWidget {
  const WeatherWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Scaffold(
      body: watch(weatherProvider('Siena')).when(
        data: (weather) => Center(
          child: Text(weather),
        ),
        loading: () => Center(
          child: const CircularProgressIndicator(),
        ),
        error: (err, stack) => Center(
          child: Text(err.toString()),
        ),
      ),
    );
  }
}

然后您当然可以使用一些状态提供程序或 ValueNotifier 等来更改您传递给weatherProvider.

我希望这会有所帮助,我知道我没有完全回答您的问题,但如果没有 AsyncValue 的力量,我不能让您继续您的 Riverpod 之旅!


推荐阅读