Flutter Redux - State Management With Redux In Flutter - Flutter Development Services in Delaware

Flutter Redux – State Management With Redux In Flutter

In this article, we’ll be focusing on the Flutter app and use Redux to manage the state of our app. Redux is a unidirectional data flow architecture that makes it easy to develop, maintain and test applications.

Redux Setup:

Now we’ll add the Redux dependencies to our app with the latest version of the package and add them to the pubspec.yaml file.

redux: ^5.0.0
flutter_redux: ^0.8.2
redux_thunk: ^0.4.0

Redux Elements:

Redux Elements - Flutter App Developer in Australia

We can see the brief explanation of each component in the following with example code. Now we move to create a modal class. We are taking the example of the login module with our flutter app. First, we need to create login_model.dart

Here is our login model: login_model.dart

class Loginmodel {
String token;

Loginmodel({this.token});

Loginmodel.fromJson(Map<String, dynamic> json) {
token = json['token'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['token'] = this.token;
return data;
}
}

We would like to store the user data after a successful login and also we would like to present a loading indicator if an API call is in progress. Also, we’d like to display an error message if an error happened. We have to compose the stored objects to match those requirements.

login_state.dart

class LoginState {
bool loading;
dynamic error;
Loginmodel success;

LoginState({
this.loading,
this.error,
this.success,

});
factory LoginState.initial() => LoginState(
loading: false,
error: null,
success: null,

);
}

We’ll create AppState this is our main state object, the one that holds the entire application’s state.

class AppState {
final LoginState login;
AppState({this.login,});
factory AppState.initial() => 
AppState(login: LoginState.initial());
AppState copyWith({LoginState login,}) {
return AppState(
login:login ?? this.login, );
}
}

Reducers

Reducer is responsible for generating the next global state of the application by transforming the information received from the actions and setting the related values in the store. A function that receives the previous state and an action containing the next State is where we use our copy with method what it does is that it’s a method that receives all State fields and returns a newState.

login_reducer.dart

LoginState loginReducer(LoginState state, dynamic action) {
  LoginState newState = state;

  switch (action.type) {

    case LOGIN_REQUEST:
      newState.error = null;
      newState.loading = true;
      newState.success = null;
      return newState;

    case LOGIN_SUCCESS:
      newState.error = null;
      newState.loading = false;
      newState.success = Loginmodel.fromJson(action.payload);
      return newState;

    case LOGIN_FAILURE:
      newState.error = action.error;
      newState.loading = false;
      newState.success = null;
      return newState;
  }
}
const LOGIN_REQUEST = 'LOGIN_REQUEST';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILURE = 'LOGIN_FAILURE';

The root reducer combines all the reducers that build different pieces of state. It’s also called app reducer.
AppState appReducer(AppState state, action) {
return AppState(
login: loginReducer(state.login, action),
);
}

Action:

Now, that we have successfully configured the store, we can start integrating the actions to initiate changes in the global state.

Actions are generated by application events and carry the information which will be consumed by the reducers. For example, if the user presses the login button we should fire an action to start the login API call and present the loading indicator.

login_action.dart

ThunkAction<AppState> login(data) => (Store<AppState> store) =>
postApi(
loginApiurl,
data,
[
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAILURE,
],
store
);

Redux Thunk is a redux middleware that helps you to dispatch functions as actions, under the hood it does one job, check that if the passed action is a function then call it and pass the store.

postApi(url, params, List<String> type, store) async {
var urlValue = BASE_URL + url;
Response response;
response = await dio.post(
urlValue,
data: params,
options: Options(
headers: {
headerAuth: headerAuthToken,
},
validateStatus: (status) { return status < 500; }
)
);
if (response.statusCode == 200) {
store.dispatch(ResponseModal.responseResult(response, type[1]));
}
else {
store.dispatch(ResponseModal.responseResult(response, type[2]));
}
}class ResponseModal {
final String type;
dynamic payload;
dynamic statuscode;
dynamic error;

ResponseModal({
this.type,
this.payload,
this.statuscode,
this.error
});

ResponseModal.responseResult(result, this.type) {
if (result != null) {
if (result is String) {
payload = result;
statuscode = null;
error =result;
} else if (result is Response) {
if (result.statusCode == 200) {
payload = result.data;
statuscode = result.statusCode;
error = false;
} else {
payload = result.data['error_msg'].toString();
statuscode = result.statusCode;
error = result.statusMessage;
}
}
} else {
payload = null;
statuscode = null;
error = null;
}
}
}

Middleware:

If we’d like to create separate layers for data handling we should use middleware. This way we are able to intercept actions and transform the information from their payload before it reaches the reducers.

void apiMiddleware(Store<AppState> store, dynamic action, NextDispatcher next) {
Timer _timer;
CancelToken _token;
_timer?.cancel();
_token?.cancel("cancelled");
_timer = new Timer(new Duration(milliseconds: 250), () {
_token = new CancelToken();
});
next(action);
}
Components:

This is the last part where we initialize our store and consume data to render our result. Components form all the visible elements on the UI and users are able to interact with them. They can be connected to our Redux store and present their current values in the UI. Flutter Redux provides a convenient way to do so by using the StoreConnector class.

converter: specifying the converter method, we will be able to observe parts of the whole Redux store and filter the component-related variables. It’s important to implement this method if we would like to avoid updating our component every time something changes in the store which can lead to performance issues.

builder: the collected properties from the converter method are passed to the builder which composes the Widget component based on those values.

login.dart

StoreConnector<AppState, LoginScreenProps>(
    converter: (store) => mapStateToProps(store),
    builder: (context, props) {
          //...... type your code
          props.loginapi(data); // call api by passing data}
);

class LoginScreenProps {
  final bool loading;
  final dynamic error;
  final Loginmodel success;
  final Function loginapi;

  LoginScreenProps({
    this.loading,
    this.error,
    this.success,
    this.loginapi,
  });
}
  LoginScreenProps mapStateToProps(Store<AppState> store) {
    return LoginScreenProps(
      loading: store.state.login.loading,
      error: store.state.login.error,
      success: store.state.login.success,
      loginapi: (data) => store.dispatch(login(data)),
    );
  }

Written By:
Rajeswari S

working as a Mobile Developer in Flutter at Optisol Business Solutions. Having good knowledge about Flutter Dart language. Experience with integrating and using analytics tools like Firebase.

Key Achievements:
  • Received appreciations from leads.​
  • Publish blog about flutter redux.​
Free Consulting