Инициализировать будущие данные в архитектуре 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 {}

WeatherState класс:

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);
}

WeatherNotifier:

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 виджета. Какие-либо предложения?

Спасибо!


person Cle    schedule 17.12.2020    source источник


Ответы (1)


Во-первых, использование 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.

Надеюсь, это поможет, я знаю, что не совсем ответил на ваш вопрос, но я не мог позволить вам продолжить путешествие по Riverpod без мощи AsyncValue!

person Alex Hartford    schedule 17.12.2020