BLoC Pattern Event using Rest API Call in Flutter

Screenshot :

BLoC Pattern Event using Rest API Call in Flutter

BLoC Pattern Event using Rest API Call in Flutter

main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'blocs/country/country_bloc.dart';
import 'blocs/user/user_bloc.dart';
import 'screens/pages/home_page.dart';
import 'service/di/dependency_injection.dart';
import 'utils/vars.dart';

void main() {
  Injector.configure(Flavor.Network);
  runApp(StartApp());
}

class StartApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(
      SystemUiOverlayStyle.dark.copyWith(
          statusBarColor: Colors.transparent,
          statusBarIconBrightness: Brightness.light),
    );

    return BlocProvider(
        builder: (context) => UserBloc(),
        child:  BlocProvider(
                builder: (context) => CountryBloc(),
                child: MaterialApp(
                    theme: ThemeData(
                        //buttonColor: Colors.white,
                        brightness: Brightness.light,
                        accentColor: Colors.orange,
                        primaryColor: Colors.orangeAccent,
                        primarySwatch: Colors.orange,
                        fontFamily: quickFont),
                    initialRoute: '/',
                    debugShowCheckedModeBanner: false,
                    routes: <String, WidgetBuilder>{
                      '/': (context) => HomePage()
                    })));
  }
}

pubspec.yaml

name: flutter_app
description: A new Flutter application.
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons:
  shared_preferences:
  intl:
  font_awesome_flutter:
  flutter_bloc:
  provider:
  rxdart:
  http:

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

  fonts:
    - family: Quicksand
      fonts:
        - asset: assets/fonts/quicksand_medium.ttf
          style: normal
        - asset: assets/fonts/quicksand_bold.otf
          weight: 700


    - family: Raleway
      fonts:
        - asset: assets/fonts/raleway_regular.ttf
          style: normal
        - asset: assets/fonts/raleway_medium.ttf

Models Package

country.dart

class CountryResponse {
  final String name;

  CountryResponse({this.name});

  CountryResponse copyWith(Map<String, dynamic> json) {
    return CountryResponse(
        name: json["name"] ?? this.name);
  }

  CountryResponse.fromJson(Map<String, dynamic> json)
      : name = json["name"];
}

login.dart

class LoginResponse {
  final double id;
  final String firstName, lastName, mobile, username, code;

  LoginResponse({
    this.id,
    this.firstName,
    this.lastName,
    this.username,
    this.code,
    this.mobile,
  });

  LoginResponse copyWith(Map<String, dynamic> json) {
    return LoginResponse(
        id: json["ID"] ?? this.id,
        firstName: json["FNAME"] ?? this.firstName,
        lastName: json["LNAME"] ?? this.lastName,
        username: json["RETAILERNAME"] ?? this.username,
        code: json["RETAILERCODE"] ?? this.code,
        mobile: json["MOBILENO"] ?? this.mobile);
  }

  LoginResponse.fromJson(Map<String, dynamic> json)
      : id = json["ID"],
        firstName = json["FNAME"],
        lastName = json["LNAME"],
        username = json["RETAILERNAME"],
        code = json["RETAILERCODE"],
        mobile = json["MOBILENO"];
}

Utils Package

validator.dart

class Validator {
  static String validateMobile(String value) {
    String pattern = r'(^[0-9]*$)';
    RegExp regExp = new RegExp(pattern);
    if (value.replaceAll(" ", "").isEmpty) {
      return 'Mobile is required';
    } else if (value.replaceAll(" ", "").length != 10) {
      return 'Mobile number must 10 digits';
    } else if (!regExp.hasMatch(value.replaceAll(" ", ""))) {
      return 'Mobile number must be digits';
    }
    return null;
  }

  static String validUserName(String value) {
    String pattern = r'(^[a-zA-Z ]*$)';
    RegExp regExp = new RegExp(pattern);
    if (value.isEmpty) {
      return 'Username is required';
    } else if (!regExp.hasMatch(value)) {
      return 'Username must be a-z and A-Z';
    }
    return null;
  }

  static String validateEmail(String value) {
    String pattern =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
    RegExp regExp = new RegExp(pattern);
    if (value.isEmpty) {
      return 'Email is required';
    } else if (!regExp.hasMatch(value)) {
      return 'Invalid email';
    } else {
      return null;
    }
  }

  static String validatePassword(String value) {
    if (value.isEmpty) {
      return 'Password is required';
    } else if (value.length < 4) {
      return 'Password must be at least 4 characters';
    }
    return null;
  }

  static String validateConfirmPassword(String value) {
    if (value.isEmpty) {
      return 'Confirm password is required';
    } else if (value.length < 4) {
      return 'Confirm password must be at least 4 characters';
    }
    return null;
  }

  static bool validationEqual(String currentValue, String checkValue) {
    if (currentValue == checkValue) {
      return true;
    } else {
      return false;
    }
  }

  static String address(String value) {
    if (value.isEmpty) {
      return 'Address is required';
    }
    return null;
  }

  static String state(String value) {
    if (value.isEmpty) {
      return 'State is required';
    }
    return null;
  }

  static String flatNoHouseNo(String value) {
    if (value.isEmpty) {
      return 'Flat No. / House No. is required';
    }
    return null;
  }

  static String city(String value) {
    if (value.isEmpty) {
      return 'City is required';
    }
    return null;
  }
}

vars.dart

String appName = "MileStone";
String quickFont = 'Quicksand';
String ralewayFont = 'Raleway';

String quickBoldFont = 'quicksand_bold.otf';
String quickNormalFont = 'quicksand_book.otf';
String quickLightFont = 'quicksand_light.otf';

String homeRoute = '/homepage';

int ok200 = 200;

Screens Package

  • pages Package
  • widgets Package

-Pages

home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/blocs/country/country_state.dart';
import 'package:flutter_app/blocs/country/country_bloc.dart';
import 'package:flutter_app/models/country.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomePage extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<HomePage> {
  CountryBloc countryBloc;

  @override
  void initState() {
    super.initState();
    countryBloc = BlocProvider.of<CountryBloc>(context);

    countryBloc.callCountry();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(
          title: Text('Country List'),
        ),
        body: Container(
            child: BlocBuilder(
                bloc: countryBloc,
                builder: (context, CountryState state) {
                  return state.loading? Center(
                      child: CircularProgressIndicator()
                  ) : ListView.builder(
                      padding: EdgeInsets.only(top: 1.0),
                      physics: BouncingScrollPhysics(),
                      itemCount: state.country.length,
                      itemBuilder: (BuildContext context, int index) {
                        CountryResponse promoter = state.country[index];
                        return Material(
                            child: Ink(
                                color: Colors.white,
                                child: ListTile(
                                    title: Text(promoter.name,
                                        style: TextStyle(
                                            color: Colors.black87,
                                            fontSize: 17)),
                                    leading: Container(
                                        width: 40,
                                        height: 40,
                                        child: Align(
                                            alignment: Alignment.center,
                                            child: Text(
                                                promoter.name
                                                    .substring(0, 1),
                                                style: TextStyle(
                                                    color: Colors.black54,
                                                    fontSize: 20))),
                                        decoration: BoxDecoration(
                                            color: Colors.grey.withOpacity(0.1),
                                            borderRadius:
                                            BorderRadius.circular(40.0))),
                                    onTap: () {})));
                      });
                })));
  }
}

-widgets

common_dialogs.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/utils/vars.dart';

/*toast(String msg) {
  Fluttertoast.showToast(
      msg: msg,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.BOTTOM,
      backgroundColor: Colors.white,
      textColor: Colors.black);
}*/

showSuccess(BuildContext context, String message, IconData icon) {
  showDialog(
      context: context,
      builder: (context) => Center(
            child: Material(
              borderRadius: BorderRadius.circular(8.0),
              color: Colors.black,
              elevation: 5.0,
              child: Padding(
                padding: const EdgeInsets.all(32.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    Icon(
                      icon,
                      color: Colors.green,
                    ),
                    SizedBox(
                      height: 10.0,
                    ),
                    Text(
                      message,
                      style: TextStyle(
                          fontFamily: '$ralewayFont', color: Colors.white),
                    )
                  ],
                ),
              ),
            ),
          ));
}

showProgress(BuildContext context) {
  showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => Center(
            child: CircularProgressIndicator()
          ));
}

hideProgress(BuildContext context) {
  Navigator.pop(context);
}

Bloc Package

  • country Package
  • user Package
  • viewmodel Package
  • common_state.dart

Bloc Package

common_state.dart

abstract class CommonState {
  bool loading = false;
  bool loaded = false;
  Map error;

  CommonState({
    this.loading,
    this.loaded,
    this.error,
  });

  Map<String, dynamic> toMap() => {
        'loading': loading,
        'loaded': loaded,
        'error': error,
      };
}

-country Package

country_bloc.dart

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter_app/blocs/viewmodel/api_provider.dart';
import 'package:flutter_app/blocs/viewmodel/country_data.dart';
import 'package:flutter_app/models/country.dart';
import 'package:flutter_app/utils/vars.dart';
import 'country_event.dart';
import 'country_state.dart';

class CountryBloc extends Bloc<CountryEvent, CountryState> {
  final ApiProvider apiProvider = ApiProvider();

  void callCountry() {
    dispatch(GetCountry());
  }

  @override
  CountryState get initialState => CountryState.initial();

  @override
  Stream<CountryState> mapEventToState(CountryEvent event) async* {
    if (event is GetCountry) {
      yield currentState.copyWith(loading: true);

      try {
        await apiProvider.getCountry();
        List<CountryResponse> brandList;
        if (apiProvider.apiResult != null) {
          brandList = apiProvider.apiResult.response;
          if (brandList.isNotEmpty) {
            yield currentState.copyWith(
              loading: false,
              country: brandList
            );
          } else {
            yield currentState.copyWith(
              error: {"error": 'Data not found'},
              loaded: false,
              loading: false,
            );
          }
        }
      } catch (e) {
        yield currentState.copyWith(
          error: {"error": "Error, Something bad happened."},
          loaded: false,
          loading: false,
        );
      }
    }
  }
}

country_event.dart

abstract class CountryEvent {}

class GetCountry extends CountryEvent {}

country_state.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter_app/models/country.dart';

class CountryState {
  final List<CountryResponse> country;
  final bool loading;
  final Map error;

  CountryState({
    @required this.loading,
    @required this.error,
    this.country,
  });

  factory CountryState.initial() {
    return CountryState(
      loading: false,
      error: null,
      country: const [],
    );
  }

  CountryState copyWith({
    bool loading,
    bool loaded,
    Map error,
    List<CountryResponse> country
  }) {
    return CountryState(
      loading: loading ?? this.loading,
      error: error ?? this.error,
      country: country ?? this.country,
    );
  }
}

-user Package

user_bloc.dart

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'user_event.dart';
import 'user_state.dart';

class UserBloc extends Bloc<UserEvent, UserState> {
  SharedPreferences pref;

  void saveUserName(username) {
    dispatch(SaveUserName(username: username));
  }

  void saveId(id) {
    dispatch(SaveId(id: id));
  }

  void saveAuthToken(token) {
    dispatch(SaveToken(token: token));
  }

  void saveEmail(email) {
    dispatch(SaveEmail(email: email));
  }

  void saveMobile(mobile) {
    dispatch(SaveMobile(mobile: mobile));
  }

  void getLoginDetails() {
    dispatch(GetLoginDetails());
  }

  @override
  UserState get initialState => UserState.initial();

  @override
  Stream<UserState> mapEventToState(
    UserEvent event,
  ) async* {
    if (event is SaveUserName) {

      pref.setString("username", event.username);
    }

    if (event is SaveId) {
      pref = await SharedPreferences.getInstance();
      pref.setString("id", event.id);
    }

    if (event is SaveEmail) {
      pref = await SharedPreferences.getInstance();
      pref.setString("email", event.email);
    }

    if (event is SaveToken) {
      pref = await SharedPreferences.getInstance();
      pref.setString("token", event.token);
    }

    if (event is RemoveToken) {
      pref = await SharedPreferences.getInstance();
      pref.remove("token");
    }

    if (event is SaveMobile) {
      pref = await SharedPreferences.getInstance();
      pref.setString("mobile", event.mobile);
    }

    //GET LOGIN DETAILS
    if (event is GetLoginDetails) {
      pref = await SharedPreferences.getInstance();
      yield currentState.copyWith(mobile: pref.getString('mobile'), userName: pref.getString('username'));
    }
  }
}

user_event.dart

abstract class UserEvent {}

class GetLoginDetails extends UserEvent {}

class SaveUserName extends UserEvent {
  final String username;
  SaveUserName({this.username});
}

class SaveId extends UserEvent {
  final String id;
  SaveId({this.id});
}

class SaveToken extends UserEvent {
  final String token;
  SaveToken({this.token});
}

class RemoveToken extends UserEvent {
  final String token;
  RemoveToken({this.token});
}

class SaveEmail extends UserEvent {
  final String email;
  SaveEmail({this.email});
}

class SaveMobile extends UserEvent {
  final String mobile;
  SaveMobile({this.mobile});
}

user_state.dart

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class UserState {
  final bool loading, loaded;
  final Map error;
  final String userName, mobile;

  UserState({
    @required this.loading,
    @required this.loaded,
    @required this.error,
    @required this.userName,
    @required this.mobile,
  });

  factory UserState.initial() {
    return UserState(
      loading: false,
      loaded: false,
      error: null,
      userName: null,
      mobile: null,
    );
  }

  UserState copyWith({
    bool loading,
    bool loaded,
    Map error,
    String userName,
    String mobile,
  }) {
    return UserState(
        loading: loading ?? this.loading,
        loaded: loaded ?? this.loaded,
        error: error ?? this.error,
        userName: userName ?? this.userName,
        mobile: mobile ?? this.mobile);
  }
}

-viewmodel Package

api_provider.dart

import 'dart:async';
import 'package:flutter_app/models/country.dart';
import 'package:flutter_app/service/abstract/api_service.dart';
import 'package:flutter_app/service/di/dependency_injection.dart';
import 'package:flutter_app/service/network_service_response.dart';

class ApiProvider {
  NetworkServiceResponse apiResult;
  APIService apiService = new Injector().flavor;

  Future<Null> getCountry() async {
    NetworkServiceResponse<List<CountryResponse>> result =
    await apiService.country();
    this.apiResult = result;
  }

}

country_data.dart

import 'package:flutter_app/models/country.dart';

class CountryViewModel {
  List<CountryResponse> promoterItems;
  CountryViewModel({this.promoterItems});

  countryList() => <CountryResponse>[
    CountryResponse(name: 'India'),
      ];
}

service Package

  • abstract Package
  • di Package
  • mock Package
  • network Package
  • network_service_response.dart
  • network_type.dart
  • restclient.dart

service Package

network_service_response.dart

class NetworkServiceResponse<T> {
  T response;
  int responseCode;

  NetworkServiceResponse({this.response, this.responseCode});
}

class MappedNetworkServiceResponse<T> {
  dynamic mappedResult;
  NetworkServiceResponse<T> networkServiceResponse;
  MappedNetworkServiceResponse(
      {this.mappedResult, this.networkServiceResponse});
}

network_type.dart

import 'package:flutter_app/service/restclient.dart';

abstract class NetworkType {
  RestClient rest;
  NetworkType(this.rest);
}

restclient.dart

import 'dart:async';
import 'package:flutter_app/utils/vars.dart';
import 'package:http/http.dart' as http;
import 'network_service_response.dart';

class RestClient {
  Future<MappedNetworkServiceResponse<T>> get<T>(String url, {Map headers}) async{
    try {
      var response = await http.get(url, headers: headers);
      return processResponse<T>(response);

    }
    catch(e) {
      return new MappedNetworkServiceResponse<T>(
          networkServiceResponse: new NetworkServiceResponse<T>(
              responseCode: 0));
    }
  }

  Future<MappedNetworkServiceResponse<T>> post<T>(String url, {Map headers, body, encoding}) async{
    try {
      var response = await http.post(url, headers: headers, body: body, encoding: encoding);
      return processResponse<T>(response);

    }
    catch(e) {
      return new MappedNetworkServiceResponse<T>(
          networkServiceResponse: new NetworkServiceResponse<T>(
              responseCode: 0));
    }
  }

  MappedNetworkServiceResponse<T> processResponse<T>(http.Response response) {
    if ((response.statusCode > ok200)) {
      return new MappedNetworkServiceResponse<T>(
          networkServiceResponse: new NetworkServiceResponse<T>(
              responseCode: response.statusCode));
    } else {
      return new MappedNetworkServiceResponse<T>(
          mappedResult: response.body,
          networkServiceResponse:
          new NetworkServiceResponse<T>(responseCode: response.statusCode));
    }
  }
}

-abstract Package

api_service.dart

import 'dart:async';
import 'package:flutter_app/models/country.dart';
import 'package:flutter_app/models/login.dart';

import '../network_service_response.dart';

abstract class APIService {
  Future<NetworkServiceResponse<List<CountryResponse>>> country();
}

-di Package

dependency_injection.dart

import 'package:flutter_app/service/abstract/api_service.dart';
import 'package:flutter_app/service/mock/mock_service.dart';
import 'package:flutter_app/service/network/network_service.dart';
import 'package:flutter_app/service/restclient.dart';

enum Flavor {Testing, Network}

//Simple DI
class Injector {
  static final Injector _singleton = new Injector._internal();
  static Flavor _flavor;

  static void configure(Flavor flavor) async {
    _flavor = flavor;
  }

  factory Injector() => _singleton;

  Injector._internal();

  APIService get flavor {
    switch (_flavor) {
      case Flavor.Testing:
        return MockService();
      default:
        return NetworkService(new RestClient());
    }
  }
}

-mock Package

mock_service.dart

import 'dart:async';

import 'package:flutter_app/models/country.dart';
import 'package:flutter_app/models/login.dart';
import 'package:flutter_app/service/abstract/api_service.dart';
import 'package:flutter_app/service/network_service_response.dart';
import 'package:flutter_app/utils/vars.dart';

class MockService implements APIService {
  @override
  Future<NetworkServiceResponse<LoginResponse>> login(
      String phoneNumber, String password) async {
    await Future.delayed(Duration(seconds: 2));
    return Future.value(NetworkServiceResponse(
        responseCode: ok200,
        response: LoginResponse(
        )));
  }

  @override
  Future<NetworkServiceResponse<List<CountryResponse>>> country() {
    return null;
  }

}

-network Package

network_service.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter_app/models/country.dart';
import 'package:flutter_app/models/login.dart';
import 'package:flutter_app/service/abstract/api_service.dart';

import '../network_service_response.dart';
import '../network_type.dart';
import '../restclient.dart';

class NetworkService extends NetworkType implements APIService {
  static final _baseURL = 'https://restcountries.eu/rest/v2/';
  static final _loginUrl = '';
  final _countryUrl = _baseURL  + 'all';


  Map<String, String> headers = {
    "Content-Type": "application/x-www-form-urlencoded"
  };

  NetworkService(RestClient rest) : super(rest);

  @override
  Future<NetworkServiceResponse<LoginResponse>> login(String phoneNumber,
      String password) async {
    var result = await rest.get<LoginResponse>(
        '$_loginUrl?MobileNo=$phoneNumber&Password=$password&EmailId=""');
    if (result.mappedResult != null) {
      var res = LoginResponse.fromJson(json.decode(result.mappedResult));
      return new NetworkServiceResponse(
          response: res,
          responseCode: result.networkServiceResponse.responseCode);
    }
    return new NetworkServiceResponse(
        responseCode: result.networkServiceResponse.responseCode);
  }

  @override
  Future<NetworkServiceResponse<List<CountryResponse>>> country() async{
    var result = await rest.get<CountryResponse>('$_countryUrl');
    if (result.networkServiceResponse.responseCode == 200) {
      List<CountryResponse> list = (json.decode(result.mappedResult) as List)
          .map((data) => new CountryResponse.fromJson(data))
          .toList();
      return new NetworkServiceResponse(
          response: list,
          responseCode: result.networkServiceResponse.responseCode
      );
    }
    return new NetworkServiceResponse(
        responseCode: result.networkServiceResponse.responseCode);
  }

}

The flutter tutorial  is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application

For more information about Flutter. visit www.fluttertutorial.in