Implement Redux with Flutter App

Maria Chojnowska

20 February 2023, 7 min read

thumbnail post

In this article, I’ll show you a simple example of using Redux with your Flutter App. Redux is an open-source JavaScript library for managing and centralizing application states. It is most commonly used with libraries such as React or Angular for building user interfaces. Similar to Facebook's Flux architecture, it was created by Dan Abramov and Andrew Clark.

Its three main principles are (source - official documentation):

  1. Single Source of truth - The global state of your application is stored in an object tree within a single store.
  2. Changes are made with pure functions - To specify how actions transform the state tree, you write pure reducers.
  3. States are read-only - The only way to change the state is to emit an action, an object describing what happened.

But enough with this theory, let’s jump strictly into practice and create a simple Flutter App (of course, the principles mentioned here can be transferred into much more complicated apps, but you have to start somewhere, right?)

do-you-need-extra-developers

Where to start?

$ flutter create sunscrapers_app

Now let’s take a look at lib/main.dart and change it as follows, mainly to define some basic configuration and usage of material design.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: Colors.blueGrey
      ),
      title: 'SunScrapers Sample App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('SunScrapers Sample App'),
        ),
        body: Center(
          child: Text('SunScrapers Sample App'),
        ),
      ),
    );
  }
}

Basically your app is ready;) But we will not stop here, so let’s move on (but in case you want to test it out, simply type flutter run to see the results).

What is next?

OK, so once the app is ready, let’s set up REDUX dependencies. We will use libraries Redux and Flutter_redux and add them to pubspec.yaml file (preferably try to use the latest versions).

dependencies:
  redux: ^5.0.0
  flutter_redux: ^0.10.0

Now we can actually set up and verify the Redux store. We will store simple boolean values for starters.

So let’s create a State Model

We need to create two files within the models folder - app_state.dart and models.dart.

The code for app_state.dart will be as follows:

import 'package:meta/meta.dart';

@immutable
class AppState {
  final bool reduxSetup;

  const AppState({
    required this.reduxSetup,
  });
@override
  String toString() {
    return 'AppState: {reduxSetup: $reduxSetup}';
  }

}

What’s important here is that we are using @immutable. This in simple words means that the state is never changed (mutated), but rather transferred from the previous state to the new one, creating a new object that will replace the old one.

Now we will use models.dart to export all models. Thanks to that, if we need to use models we will only import the models.dart file - which will be really helpful if the app grows bigger and the amount of models will multiply.

export 'app_state.dart';

Now let’s create actions

They are simply objects that have a type defined and some payload if necessary. Type is basically the intent of the action and payload is naturally the data that comes with it.

For now, we will store the actions in one file: lib/actions/actions.dart

class SomeAction {
  final bool somePayload;

  SomeAction(this.somePayload);
}

As we can see the payload (data) in this case is a simple boolean.

Let’s continue with Reducers

The main reducer will consist of all reducers that take care of different parts of the state. We must create the reducers directory and two files: app_reducer.dart and sample_reducer.dart.

App_Reducer file will consist of following code:

import 'package:sunscrapers_app/models/models.dart';
import 'package:sunscrapers_app/reducers/sample_reducer.dart';

AppState appReducer(AppState state, action) {
  return AppState(
    reduxSetup: sampleReducer(state.reduxSetup, action),
  );
}

And now let’s take a look at sample_reducer.dart file

import 'package:redux/redux.dart';
import 'package:sunscrapers_app/actions/actions.dart';

final sampleReducer = TypedReducer<bool, SampleAction>(_sampleActionReducer);

bool _sampleActionReducer(bool state, SampleAction action) {
  return action.samplePayload;
}

This is a place that receives the SampleAction and returns a new state depending on the data received. The function that is passed to the TypedReducer is the actual reducer function that returns the new state.

Now we can finally define the Store.

In order to achieve that we need to update the main.dart file. It should look similar to this:

import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:sunscrapers_app/models/models.dart';
import 'package:sunscrapers_app/reducers/app_reducer.dart';

void main() {
  final store = Store<AppState>(
    appReducer,
    initialState: AppState(reduxSetup: true),
  );

  runApp(StoreProvider(store: store, child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: Colors.blueGrey
      ),
      title: 'SunScrapers Sample App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('SunScrapers Sample App'),
        ),
        body: Center(
          child: Text('SunScrapers Sample App'),
        ),
      ),
    );
  }
}

We have defined the store with the app reducer and its initial state. Before passing our app widget to the runApp function we wrap it inside the StoreProvider to ensure that store is propagated to all the widgets in the widget tree.

Does the app work?

Now that we have a Redux store we should print the app state to see if it works.

So let’s add following code to main.dart:

void main() {
  final store = Store<AppState>(
    appReducer,
    initialState: AppState(reduxSetup: true),
  );

  print('The initials state is: ${store.state}');

  runApp(StoreProvider(store: store, child: MyApp()));
}

Flutter log should look like this: Initial state: AppState {reduxSetup: true}

Now that we see everything is working we should update main.dart to connect it to the ReduxStore:

import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:sunscrapers_app/models/models.dart';
import 'package:sunscrapers_app/reducers/app_reducer.dart';
import 'package:sunscrapers_app/actions/actions.dart'; // import actions

void main() {
  final store = Store<AppState>(
    appReducer,
    initialState: AppState(reduxSetup: false), // Set initial state to false
  );

  print('Initial state: ${store.state}');

  runApp(StoreProvider(store: store, child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
theme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: Colors.blueGrey
      ),      
title: 'SunScrapers Sample App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('SunScrapers Sample App'),
        ),
        // Connect to the store
        body: StoreConnector<AppState, bool>(
          converter: (Store<AppState> store) => store.state.reduxSetup,
          builder: (BuildContext context, bool reduxSetup) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.left,
                children: <Widget>[
                  Text('Redux is working: $reduxSetup'),
                  ElevatedButton(
                    child: Text('Dispatch the action'),
                    onPressed: () => StoreProvider.of<AppState>(context)
                        .dispatch(SampleAction(true)),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

As you can see StoreConnector requires a converter (a function that receives the store and returns the part of the state that we need).

We have also created a dispatcher. If you rerun the app and press the button, the state should be updated, and rebuild the widget to show the new data.

Summing up

This above is the simplest possible implementation of Redux within the Flutter app - I wanted to give you a brief idea of what Redux can do. You can use it to do much more complicated things, but that would be a topic for another article.

Contact us

At Sunscrapers, we have a team of developers and software engineers with outstanding technical knowledge and experience. We can build your project successfully.

Talk with us about hiring Python and Django developers and work with the best. Sunscrapers can help you make the most out of Python, Django, and other technologies to put your project to the next level.

Contact us at hello@sunscrapers.com

Are you ready for your next project?

Whether you need a full product, consulting, tech investment or an extended team, our experts will help you find the best solutions.

Hi there, we use cookies to provide you with an amazing experience on our site. If you continue without changing the settings, we’ll assume that you’re happy to receive all cookies on Sunscrapers website. You can change your cookie settings at any time.