Flutter_riverpod package

I am writing this post, because I belive it is important to have some solid tutorial that explains something little simpler than the rest.

I learned the things that i know from reso coder and other youtube tutorials

What is Riverpod?

Riverpod is a state management solution for flutter/dart.

It is scalable and if you follow some design principles, it is robust and organised.

In this guide, we will be using flutter_riverpod package, but there are also riverpod and hooks_riverpod, but we wont be covering those here

Implementing it in project

Import dependency of flutter_riverpod

flutter pub add flutter_riverpod

Create the model of the data stored

in our example we would make Weather App, so it is weather in some location

class Weather {
  final String cityName;
  final double temperature;
    required this.cityName,
    required this.temperature,

Create WeatherState

we will make an absctract class, that could not be instanciated, but can be implemented by others, it is used here, because we can also say that our state will be WeatherState() and it could be all of the others, that implement it

abstract class WeatherState {
  const WeatherState();

and under it, create all the possible states, with the WeatherLoaded actualy carrying Weather as a Parameter, and WeatherError carrying message

class WeatherInitial implements WeatherState {
  const WeatherInitial();

class WeatherLoading implements WeatherState {
  const WeatherLoading();

class WeatherLoaded implements WeatherState {
  final Weather weather;
  const WeatherLoaded(this.weather);
  bool operator ==(Object o) {
    if (identical(this, o)) return true;
    return o is WeatherLoaded && o.weather == weather;

  int get hashCode => weather.hashCode;

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

we should also @override equals on two so that we could differenciate changes by actual value, and not by its reference
also we @override the hashcode getter, so that we would not bump in any errors that could occur in some rare cases

Create WeatherRepository

we need to get the data, from the Some backend API, or from fake one.
we make the WeatherRepository abstract, so that we know what methods we need to implement, but in real app scenario, we would not make it abstract, as it would probably be implementing the fetching from real backend, and Fake repository could be exactly the same.

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

class FakeWeatherRepository implements WeatherRepository {
  Future<Weather> fetchWeather(String city) {
    //job of getting the data from the backend
    //delay one second to simulate the network delay
    return Future.delayed(
      const Duration(seconds: 1),
      () {
        Random random = Random();
        if (random.nextBool()) {
          throw Exception();
        //throw Exception();
        int randomTemperature = random.nextInt(40);
        return Weather(
            cityName: city, temperature: randomTemperature.toDouble());

Create WeatherNotifier

class that extends StateNotifier<WeatherState> that says to the Flutter widgets what actions we can do and holds the Single state

class WeatherNotifier extends StateNotifier<WeatherState> {
  final WeatherRepository _weatherRepository;
  WeatherNotifier(this._weatherRepository) : super(const WeatherInitial());
  Future<void> fetchWeather(String cityName) async {
    state = const WeatherLoading();
    try {
      final weather = await _weatherRepository.fetchWeather(cityName);
      state = WeatherLoaded(weather);
    } on Exception catch (_) {
      state = const WeatherError("Couldn't fetch weather.");
 WeatherNotifier(this._weatherRepository) : super(const WeatherInitial());

is the inicializer part, that when you instanciate it later in a provider, you can say, that it uses fake or real weather repository.

we use super(const WeatherInitial()) to call the constructor of the parent class to initialize the properties from the parent class- Shortly, initial state

Add providers

weather repository provider for providing us with the data fetching
it uses Provider<repository>

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

weatherNotifierProvider which uses StateNotifier Provider with a ref.watch on the state of the weatherRepositoryProvider that we inicialized in the step before

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

Implementing the flutter UI

Wrap the Material app in ProviderScope

      child: MaterialApp(
        title: 'flutter_riverpod_example',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        home: const ExampleHomePage(),

Create ExampleHomePage

because we are using riverpod, we can now in most cases just use StatelessWidgets
use StatefullWidgets only for the components with some input or functionality that is not implemented using riverpod

// stateless widget, because there is only one TextField, and it can use onSubmitted instead of having custom TextEditingController
class ExampleHomePage extends StatelessWidget {
  const ExampleHomePage({

  Widget build(BuildContext context) {
  // we use scaffold to make a whole new page
    return Scaffold(
    // we do not need appbar, so we go straignt into implementation of Body
    // Center all child widgets
      body: Center(
      // child should be column, and the height should be minimum, based on         content
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // consumer, so we would get the ref and get the WeatherNotifier
            Consumer(builder: (context, ref, child) {
             // location input
              return TextField(
                decoration: const InputDecoration(
                  hintText: 'Enter a city',
                  border: OutlineInputBorder(),
                onSubmitted: (value) {
                // we get the method for handling the fetching and setting of states from weatherNotifierProvider
                // we want to only read the notifier because watch could trigger rebuilds that are not needed
            // we use consumer for the weather State
            // it is important to use watch so it rerenders on change
            // for type safe code, we have to ask, if the WeatherState is initial, loading, error, or loaded and handle the actions accordingly, if we would not check if it is the state type, we would get error, that the state.weather is undefined.
              builder: (context, ref, child) {
                final state = ref.watch(weatherNotifierProvider);
                if (state is WeatherInitial) {
                  return const Text('Please Select a Location');
                if (state is WeatherLoading) {
                  return const CircularProgressIndicator();
                if (state is WeatherLoaded) {
                  final weather = state.weather;
                  return Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                        style: Theme.of(context).textTheme.headlineMedium,
                        '${weather.temperature.toStringAsFixed(1)} °C',
                        style: Theme.of(context).textTheme.headlineLarge,
                      const SizedBox(height: 20),
                if (state is WeatherError) {
                  return Text(
                    style: const TextStyle(color: Colors.red),
                return const Text('Something went wrong!');

If something is not clear, or outdated?
Comment it, so that we could all get better!



Leave a Reply

Your email address will not be published. Required fields are marked *