DEV Community

Cover image for How can we integrate Flutter with .Net core web API | Part-2: Building the Flutter App.
Yared Solomon
Yared Solomon

Posted on

How can we integrate Flutter with .Net core web API | Part-2: Building the Flutter App.

The application simply do the following activities.

  • Displaying the Services on the main screen
  • Showing the detail of every service listed on the main screen
  • It will have edit option for every service
  • It will have a delete option for every service.

This is the main page of the application that will display the services lists.

Alt Text

and this is the service add and update page.

Alt Text

and this one is the service detail page on this page we can delete and edit the single service.

Alt Text

so here we will follow some steps to buildthis application.

Pre requests

1.Installing Android studio
2.Installing Flutter and dart

Step 1: Creating a new Flutter Project from an android studio or visual studio code.
From the android studio go to File and click on the New then select New Flutter Project option.

image

Then you will get the following page.
Click on the Flutter Application and click on the Next button

image
then here give the application a new name. and click on the Next button.

image
Then click on the Finish button at the bottom.
image
here we get a default code.

image

First, we should prepare a debugging emulator or connect our phone. and it will come at the top.

image

after that click on the green run button at the top. you will get this page on the emulator.

Alt Text

leave this code, for now, we will update it later.
now create a new directory called Service inside the lib package and inside this directory create the following directories.

image

Let us see step by step the code insides these directories.

What is Bloc State Management?

Bloc Overview

Overall, Bloc attempts to make state changes predictable by regulating when a state change can occur and enforcing a single way to change state throughout an entire application.

Below are common developer needs that Bloc tries to address

  • Working as efficiently as possible and reuse components both within your application and across other applications.
  • Letting many developers to seamlessly work within a single code base
  • following the same patterns and conventions Developing fast and reactive apps
  • knowing what state your application is in at any point in time
  • Easily testing every case to make sure our app is responding appropriately
  • Recording every single user interaction in your application so that you can make data-driven decisions

Installing and Importing Bloc package

Installation
Add the flutter_bloc package to your pubspec.yaml as a dependency

dependencies:
flutter_bloc: ^6.1.1

Import

import 'package:flutter_bloc/flutter_bloc.dart';
Enter fullscreen mode Exit fullscreen mode

Bloc Architecture

image

Bloc Architecture: Data Layer

The data layer's responsibility is to retrieve/manipulate data from one or more sources The data layer can be split into two parts
Repository
Data Provider
This layer is the lowest level of the application and interacts with databases, network requests, and other asynchronous data sources

Bloc Architecture: Data Layer -> Data Provider

The data provider's responsibility is to provide raw data. It should be generic and versatile and it will usually expose simple APIs to perform CRUD operations. We might have a createData, readData, updateData, and deleteData method as part of our data layer.

Inside the DataProvider directory create the following two dart files.

image

Service_data.dart

then inside the Service_data.dart file first add the necessary imports.

import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_group_project/Service/Model/Service.dart';
import 'package:meta/meta.dart';
import 'package:http/http.dart' as HTTP;
Enter fullscreen mode Exit fullscreen mode

create a class called ServiceDataProvider

class ServiceDataProvider {

}
Enter fullscreen mode Exit fullscreen mode

then inside this class first define the Url for which it will fetch the data from the back-end API

final _baseUrl = 'http://192.168.42.232:5000/api';
Enter fullscreen mode Exit fullscreen mode

Then define the httpClient


final http.Client httpClient;

Enter fullscreen mode Exit fullscreen mode

next, add the constructor for the class.and inside the constructor specify the necessary parameter .

ServiceDataProvider({@required this.httpClient}) : assert(httpClient != null);
Enter fullscreen mode Exit fullscreen mode

after this let's we add the code for the CRUD methods.

createService
this method will communicate with the back-end in order to create the data at the back-end database.

First we have to define the method signature for the createService method as follows.

Future<Service> createService(Service service) async {

}
Enter fullscreen mode Exit fullscreen mode

then let us define the response


final response = await httpClient.post(

)
Enter fullscreen mode Exit fullscreen mode

Inside this httpClient.post method we should put some parameters which are essesntial for the http call.

URL
The first one is the URL this is the destination where the method invocation will go.

 Uri.http('http://192.168.42.232:5000', '/api/services'),
Enter fullscreen mode Exit fullscreen mode

Header

then we should give the header information. on this information we have to tell the back-end the
type of files we are passing.

headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
Enter fullscreen mode Exit fullscreen mode

Body

then the last on is the body this will be the data that will be created at the server.

 body: jsonEncode(<String, dynamic>{
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
Enter fullscreen mode Exit fullscreen mode

The whole response code will look like the following.

 final response = await httpClient.post(
      Uri.http('http://192.168.42.232:5000', '/api/services'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },

      body: jsonEncode(<String, dynamic>{
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
    );
Enter fullscreen mode Exit fullscreen mode

after the response successfully define, based on the response that come from the server we will update the state

if (response.statusCode == 200) {
      return Service.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to create course.');
    }
Enter fullscreen mode Exit fullscreen mode

The whole createService method code looks like this.


 Future<Service> createService(Service service) async {

    final response = await httpClient.post(
      Uri.http('http://192.168.42.232:5000', '/api/services'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },

      body: jsonEncode(<String, dynamic>{
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
    );

    if (response.statusCode == 200) {
      return Service.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to create course.');
    }
  }
Enter fullscreen mode Exit fullscreen mode

getService


Future<List<Service>> getServices() async {
    try{

     final response = await http.get('${_baseUrl}/services');
     if (response.statusCode == 200) {
       final services = jsonDecode(response.body) as List;
       return services.map((service) => Service.fromJson(service)).toList();
     } else {
       throw Exception('Failed to load courses');
     }
   }catch(e){
     print("Exception throuwn $e");
    }


  }
Enter fullscreen mode Exit fullscreen mode

deleteService

Future<void> deleteService(int  id) async {
    final http.Response response = await http.delete(
      'http://192.168.43.163:5000/Service/$id',
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to delete course.');
    }

  }
Enter fullscreen mode Exit fullscreen mode

updateService

Future<void> updateService(Service service) async {
    final http.Response response = await httpClient.put(
      'http://192.168.43.163:5000/Service/',
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, dynamic>{
        "serviceId":service.id,
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to update course.');
    }
  }
Enter fullscreen mode Exit fullscreen mode

The overall service_data.dart code will be like the following.

import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_group_project/Service/Model/Service.dart';
import 'package:meta/meta.dart';
import 'package:http/http.dart' as http;

class ServiceDataProvider {
  final _baseUrl = 'http://192.168.42.232:5000/api';
  final http.Client httpClient;

  ServiceDataProvider({@required this.httpClient}) : assert(httpClient != null);

  Future<Service> createService(Service service) async {

    final response = await httpClient.post(
      Uri.http('http://192.168.42.232:5000', '/api/services'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },

      body: jsonEncode(<String, dynamic>{
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
    );

    if (response.statusCode == 200) {
      return Service.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to create course.');
    }
  }

  Future<List<Service>> getServices() async {
    try{

     final response = await http.get('${_baseUrl}/services');
     if (response.statusCode == 200) {
       final services = jsonDecode(response.body) as List;
       return services.map((service) => Service.fromJson(service)).toList();
     } else {
       throw Exception('Failed to load courses');
     }
   }catch(e){
     print("Exception throuwn $e");
    }


  }

  Future<void> deleteService(int  id) async {
    final http.Response response = await http.delete(
      'http://192.168.43.163:5000/Service/$id',
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to delete course.');
    }

  }

  Future<void> updateService(Service service) async {
    final http.Response response = await httpClient.put(
      'http://192.168.43.163:5000/Service/',
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, dynamic>{
        "serviceId":service.id,
        "serviceName": service.ServiceName,
        "description": service.Description,
        "category": service.Category,
        "initialPrice": service.InitialPrice,
        "intermediatePrice":service.IntermediatePrice,
        "advancedPrice":service.AdvancedPrice,

      }),
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to update course.');
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

data_provider.dart
this will export the Service_data add the following line of code inside.

export 'Service_data.dart';
Enter fullscreen mode Exit fullscreen mode

Bloc Architecture: Data Layer -> Repository

The repository layer is a wrapper around one or more data providers with which the Bloc Layer communicates. It can interact with multiple data providers and perform transformations on the data before handing the result to the business logic layer.

Inside the Repository directory let us create the following two dart files.

image

Then inside the Service_repository.dart file we are going to create a function for all of the data provider CRUD method.
this method will serve as a mediator between the business logic layer and the data access layer.

import 'package:flutter_group_project/Service/Model/Service.dart';
import 'package:meta/meta.dart';
import '../Service.dart';

class ServiceRepository {
  final ServiceDataProvider dataProvider;

  ServiceRepository({@required this.dataProvider})
      : assert(dataProvider != null);

  Future<Service> createService(Service service) async {
    return await dataProvider.createService(service);
  }

  Future<List<Service>> getServices() async {
    print("This is the getService method");
    return await dataProvider.getServices();
  }

  Future<void> updateService(Service service) async {
    await dataProvider.updateService(service);
  }

  Future<void> deleteService(int  id) async {
    await dataProvider.deleteService(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

The repository.dart file does nothing but exporting the Service_repository.dart.

export 'Service_repository.dart';
Enter fullscreen mode Exit fullscreen mode

Bloc Architecture: Business Logic Layer

The business logic layer's responsibility is to respond to input from the presentation layer with new states. This layer can depend on one or more repositories to retrieve data needed to
build up the application state. Think of the business logic layer as the bridge between the user interface (presentation layer) and the data layer.
The business logic layer is notified of events/actions from the presentation layer and then communicates with the repository in order to build a new state for the presentation layer to consume.

From the Bloc directory create the following dart files.

image

The bloc state management has three different elements.

  • State
  • Event
  • bloc

State
The State will represent the state for the application. this will be the defined state that the app will possess.

open Service_state.dart file and add the following code inside.

import 'package:equatable/equatable.dart';
import 'package:flutter_group_project/Service/Service.dart';


class ServiceState extends Equatable {
  const ServiceState();

  @override
  List<Object> get props => [];
}
class ServiceLoading extends ServiceState {}

class ServicesLoadSuccess extends ServiceState{
  final List<Service> services;

  ServicesLoadSuccess([this.services = const []]);

  @override
  List<Object> get props => [services];
}

class ServiceOperationFailure extends ServiceState {}
Enter fullscreen mode Exit fullscreen mode

As you can see from the code we have three different states. ServiceLoading,ServicesLoadSuccess,ServiceOperationFailer

Event
The event means the operation that will take place in the application. this will be the same as the CRUD operation that we have registered in the data provider.

For this open the Service_event.dart file and add the following code inside.

import 'package:equatable/equatable.dart';
import 'package:flutter_group_project/Service/Model/Service.dart';
import '';

abstract class ServiceEvent extends Equatable {
  const ServiceEvent();
}

class ServiceLoad extends ServiceEvent {
  const ServiceLoad();

  @override
  List<Object> get props => [];
}

class ServiceCreate extends ServiceEvent {
  final Service service;

  const ServiceCreate(this.service);

  @override
  List<Object> get props => [service];

  @override
  String toString() => 'Service Created {service: $service}';
}

class ServiceUpdate extends ServiceEvent {
  final Service service;

  const ServiceUpdate(this.service);

  @override
  List<Object> get props => [service];

  @override
  String toString() => 'Service Updated {service: $service}';
}

class ServiceDelete extends ServiceEvent {
  final Service service;

  const ServiceDelete(this.service);

  @override
  List<Object> get props => [service];

  @override
  toString() => 'Service Deleted {service: $service}';
}
Enter fullscreen mode Exit fullscreen mode

here we have 4 different Events These are listed below with their correspondence data provider method.

  • ServiceLoad -> getServices()
  • ServiceCreate -> createService()
  • ServiceUpdate -> updateService()
  • ServiceDelete -> deleteService()

bloc

This dart file will map every event invoked by the presentation layer to the state. then it will send the updated state back to the presentation layer.

open the Service_bloc.dart file and add the following code.

import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:flutter_group_project/Service/Bloc/bloc.dart';
import 'package:flutter_group_project/Service/Repository/Service_repository.dart';


class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
  final ServiceRepository serviceRepository;

  ServiceBloc({@required this.serviceRepository})
      : assert(serviceRepository != null),
        super(ServiceLoading());

  @override
  Stream<ServiceState> mapEventToState(ServiceEvent event) async* {
    if (event is ServiceLoad) {
      print("Service load method");
      yield ServiceLoading();

      try {
        final services = await serviceRepository.getServices();
        print("This is the service $services");
        yield ServicesLoadSuccess(services);
      } catch (_) {
        yield ServiceOperationFailure();
      }
    }

    if (event is ServiceCreate) {
      try {
        await serviceRepository.createService(event.service);
        final services = await serviceRepository.getServices();
        yield ServicesLoadSuccess(services);
      } catch (_) {
        yield ServiceOperationFailure();
      }
    }

    if (event is ServiceUpdate) {
      try {
        await serviceRepository.updateService(event.service);
        final services = await serviceRepository.getServices();
        yield ServicesLoadSuccess(services);
      } catch (_) {
        yield ServiceOperationFailure();
      }
    }

    if (event is ServiceDelete) {
      try {
        await serviceRepository.deleteService(event.service.id);
        final services = await serviceRepository.getServices();
        yield ServicesLoadSuccess(services);
      } catch (e) {
        print("Error de,ete=$e");
        yield ServiceOperationFailure();
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

and finally, there is one file that remained called bloc.dart this file will export all three files once. so in other than importing all three files separately importing only this file will make our code simpler and minimize the line of code.

export 'Service_bloc.dart';
export 'Service_state.dart';
export 'Service_event.dart';
Enter fullscreen mode Exit fullscreen mode

If you came from the react js web development background, this type of state management is exactly the same as the React Redux state management

  • Bloc -> Reducer
  • State -> State
  • Event -> Action

Bloc Architecture: Presentation Layer

The presentation layer's responsibility is to figure out how to render itself based on one or more bloc states In addition, it should handle user input and application lifecycle events Most applications flows will start with an AppStart event which triggers the application to fetch some data to present to the user
In this scenario, the presentation layer would add an AppStart event.

We are going to build the UI on this step.
In the Screen directory, create the following dart files.

image

The main page of the app looks like the following.

Alt Text

First, there will be an import from the above. we are gonna import all the necessary packages we need to integrate the UI with the business logic and for layouts.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_group_project/Service/Bloc/bloc.dart';
import 'package:flutter_group_project/Service/Screen/ServiceCreateUpdateScreen.dart';
import 'package:flutter_group_project/Service/Screen/screens.dart';
import 'dart:math';

Enter fullscreen mode Exit fullscreen mode

Create a class called ServiceMainScreen and define the class routeName as Static.

class ServiceMainScreen extends StatelessWidget {
  static const routeName='/category_screen';
}
Enter fullscreen mode Exit fullscreen mode

The first method we are going to use is a buildSectionTitle and this method is used to build a custom text widget for the title.

Widget buildSectionTitle(BuildContext context, String text){
    return  Container(

      margin: EdgeInsets.symmetric(
        vertical: 10,

      ),
      child: Text('${text}',
        style: Theme.of(context).textTheme.headline6,),
    );
  }
Enter fullscreen mode Exit fullscreen mode

and next, we have here a buildContainer method that used to build a container, we will use this method later.


Widget buildContainer(BuildContext context , Widget child){
    return Container(
      height: MediaQuery.of(context).size.height * 0.5 ,
      width:MediaQuery.of(context).size.width * 0.90 ,
      decoration: BoxDecoration(
        color:Colors.white,
        border:Border.all(
            color:Colors.grey
        ),
        borderRadius: BorderRadius.circular(10),


      ),
      child:child,
    );
  }
Enter fullscreen mode Exit fullscreen mode

Build Method
This is the defualt method that will be overriden. this method will start rendering the application so that the app start the building process from this method.

It have a BuildContext parameter.


 @override
  Widget build(BuildContext context) {
    Random random = new Random();
    int randomNumber = random.nextInt(6);

    // return statment here
}
Enter fullscreen mode Exit fullscreen mode

we will use the random number later.

the build methos should return a widget.we are going to return a Scaffold from the build method.

return Scaffold(
// 
)
Enter fullscreen mode Exit fullscreen mode

the scaffold widget have might have different named parameters depending on the app we are building for this application we are
going to use the following named parameters inside the Scaffold widget.

  • appBar
  • body
  • floatingActionbutton
  • floatingActionButtonLocation
  • bottomNavigationBar

appBar

This is the top navigation bar.
image

appBar: AppBar(title: Text('Category Name'), actions: [
          PopupMenuButton(
              icon: Icon(Icons.refresh),
              itemBuilder: (_) => [
                    PopupMenuItem(
                      child: Text('Refresh'),
                      value: () {},
                    ),
                    PopupMenuItem(
                      child: Text('Show All'),
                      value: null,
                    ),
                  ]),
        ]),
Enter fullscreen mode Exit fullscreen mode

body
This is the main portion of the application, it will cover the place other than the bottomNavigationBar and the floatingActionButton

image

Because the whole page will scroll we have to held using a singleChildScrolView at the top.

body: SingleChildScrollView(
          child: Column(
              children: [
                 // Childrens will come here
               ]
          )
Enter fullscreen mode Exit fullscreen mode

Then inside the children widget we have the following childrent respectively.
* Image Section
* Text Section
* List View

Image Section

image

 Container(
              height: MediaQuery.of(context).size.width * 0.5,
              width: double.infinity,
              child: Image.asset("Assets/assets/wood.jpg"),
            ),
Enter fullscreen mode Exit fullscreen mode

Text Section

image

buildSectionTitle(context, 'Services'),
Enter fullscreen mode Exit fullscreen mode

If you remember in the above we have implemented this buildSectionTitle method, so that we are using it here to build a custome text Wdiget.

List View
image

This section is the place where the Services that came from the back-end will be displayed, so this widget is a replaceble widget depending on the new state that came.
we are going to use our Bloc concept here.

Core Concepts of Bloc: BlocBuilder

BlocBuilder is a Flutter widget which requires a Bloc and a builder function BlocBuilder handles building the widget in response to new states The builder function will potentially be called many times and should be a pure function that returns a widget in response to the state.

Next to the Service title section put the following blocBuilder method signature.

    BlocBuilder<ServiceBloc, ServiceState>(builder: (_, state) {
        // remainging code here
    }
Enter fullscreen mode Exit fullscreen mode

Then inside this method let us add our business logic, this page only loads data from the businessLogicLayer so the possible states will be the following.

  • ServiceOperationFailure
  • ServicesLoadSuccess

so let us do a confition for these states.

ServiceOperationFailure
we do not have much to do here only return a Text.

 if (state is ServiceOperationFailure) {
                return Text('Could not do Service  operation');
              }
Enter fullscreen mode Exit fullscreen mode

ServicesLoadSuccess
here our on this condition our list view will be build depending on the States that come from businessLogicLayer.

if (state is ServicesLoadSuccess) {
                final services = state.services;
                return buildContainer(
                    context,
                    ListView.builder(
                        itemCount: services.length,
                        itemBuilder: (ctx, index) => Column(children: [
                              Container(
                                decoration: BoxDecoration(
                                    color: Colors.white38,
                                    borderRadius: BorderRadius.circular(20)),
                                height:
                                    MediaQuery.of(context).size.height * 0.15,
                                margin: EdgeInsets.all(4),
                                padding: EdgeInsets.all(4),
                                child: Card(
                                  elevation: 5,
                                  margin: EdgeInsets.symmetric(
                                      vertical: 8, horizontal: 5),
                                  child: Center(
                                    child: ListTile(
                                      leading: CircleAvatar(
                                        backgroundImage: AssetImage(
                                            'Assets/assets/fixit.png'),
                                      ),
                                      title: Text(
                                        services[index].ServiceName != null
                                            ? services[index].ServiceName
                                            : "place holder",
                                        style: Theme.of(context)
                                            .textTheme
                                            .headline6,
                                      ),
                                      subtitle: Text(
                                        services[index].Description != null
                                            ? services[index].Description
                                            : "Place holder",
                                      ),
                                      trailing: MediaQuery.of(context)
                                                  .size
                                                  .width >
                                              450
                                          ? FlatButton.icon(
                                              textColor:
                                                  Theme.of(context).errorColor,
                                              icon: Icon(
                                                Icons.delete_forever,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            )
                                          : IconButton(
                                              icon: Icon(
                                                Icons.star_border,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            ),
                                      onTap: () => Navigator.of(context)
                                          .pushNamed(ServiceDetail.routeName,
                                              arguments: services[index]),
                                    ),

                                  ),
                                ),
                              )
                            ])));
              }
Enter fullscreen mode Exit fullscreen mode

then on the defualt position let us return a progress indicator.

return CircularProgressIndicator();
Enter fullscreen mode Exit fullscreen mode

The overall BlocBuilder code will looks like the following.

            BlocBuilder<ServiceBloc, ServiceState>(builder: (_, state) {

              if (state is ServiceOperationFailure) {
                return Text('Could not do Service  operation');
              }
              if (state is ServicesLoadSuccess) {
                final services = state.services;
                return buildContainer(
                    context,
                    ListView.builder(
                        itemCount: services.length,
                        itemBuilder: (ctx, index) => Column(children: [
                              Container(
                                decoration: BoxDecoration(
                                    color: Colors.white38,
                                    borderRadius: BorderRadius.circular(20)),
                                height:
                                    MediaQuery.of(context).size.height * 0.15,
                                margin: EdgeInsets.all(4),
                                padding: EdgeInsets.all(4),
                                child: Card(
                                  elevation: 5,
                                  margin: EdgeInsets.symmetric(
                                      vertical: 8, horizontal: 5),
                                  child: Center(
                                    child: ListTile(
                                      leading: CircleAvatar(
                                        backgroundImage: AssetImage(
                                            'Assets/assets/fixit.png'),
                                      ),
                                      title: Text(
                                        services[index].ServiceName != null
                                            ? services[index].ServiceName
                                            : "place holder",
                                        style: Theme.of(context)
                                            .textTheme
                                            .headline6,
                                      ),
                                      subtitle: Text(
                                        services[index].Description != null
                                            ? services[index].Description
                                            : "Place holder",
                                      ),
                                      trailing: MediaQuery.of(context)
                                                  .size
                                                  .width >
                                              450
                                          ? FlatButton.icon(
                                              textColor:
                                                  Theme.of(context).errorColor,
                                              icon: Icon(
                                                Icons.delete_forever,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            )
                                          : IconButton(
                                              icon: Icon(
                                                Icons.star_border,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            ),
                                      onTap: () => Navigator.of(context)
                                          .pushNamed(ServiceDetail.routeName,
                                              arguments: services[index]),
                                    ),

                                  ),
                                ),
                              )
                            ])));
              }
              return CircularProgressIndicator();
            }),
Enter fullscreen mode Exit fullscreen mode

So by this we have finalized the body.

floatingActionButton
image
This action button will be use to add additional services.

floatingActionButton: FloatingActionButton(
          onPressed: () => Navigator.of(context).pushNamed(
            AddUpdateService.routeName,
            arguments: ServiceArgument(edit: false),
          ),
          child: Icon(Icons.add),
        ),
Enter fullscreen mode Exit fullscreen mode

To make the floatingActionButton have a dom shaped and stayed at the center of the bottom navigation bar let us give it the follwoing property.

floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
Enter fullscreen mode Exit fullscreen mode

bottomNavigationBar

This part have no any functionality for this application but incase of a serious project we can give it some actoin to perform. here it is just a default for a decorating the layout.

image

bottomNavigationBar: BottomAppBar(
          color: Theme.of(context).primaryColor,
          shape: CircularNotchedRectangle(),
          child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                IconButton(
                  icon: Icon(Icons.search),
                  color: Colors.white,
                  onPressed: () {
                    print("search icon button have been clicked");
                  },
                ),
                IconButton(
                  icon: Icon(Icons.note),
                  color: Colors.white,
                  onPressed: () {
                    print("the note icon button have been clicked");
                  },
                )
              ]),
        )
Enter fullscreen mode Exit fullscreen mode

The whole ServiceMainScreen.dart file will be as the follwoing.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_group_project/Service/Bloc/bloc.dart';
import 'package:flutter_group_project/Service/Screen/ServiceCreateUpdateScreen.dart';
import 'package:flutter_group_project/Service/Screen/screens.dart';
import 'dart:math';

class ServiceMainScreen extends StatelessWidget {
  static const routeName = '/category_screen';

  Widget buildSectionTitle(BuildContext context, String text) {
    return Container(
      margin: EdgeInsets.symmetric(
        vertical: 10,
      ),
      child: Text(
        '${text}',
        style: Theme.of(context).textTheme.headline6,
      ),
    );
  }

  Widget buildContainer(BuildContext context, Widget child) {
    return Container(
      height: MediaQuery.of(context).size.height * 0.5,
      width: MediaQuery.of(context).size.width * 0.90,
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border.all(color: Colors.grey),
        borderRadius: BorderRadius.circular(10),
      ),
      child: child,
    );
  }

  @override
  Widget build(BuildContext context) {
    Random random = new Random();
    int randomNumber = random.nextInt(6);

    print(randomNumber);

    return Scaffold(
        appBar: AppBar(title: Text('Category Name'), actions: [
          PopupMenuButton(
              icon: Icon(Icons.refresh),
              itemBuilder: (_) => [
                    PopupMenuItem(
                      child: Text('Refresh'),
                      value: () {},
                    ),
                    PopupMenuItem(
                      child: Text('Show All'),
                      value: null,
                    ),
                  ]),
        ]),
        body: SingleChildScrollView(
          child: Column(children: [
            Container(
              height: MediaQuery.of(context).size.width * 0.5,
              width: double.infinity,
              child: Image.asset("Assets/assets/wood.jpg"),
            ),
//           ingredient
            buildSectionTitle(context, 'Services'),

            BlocBuilder<ServiceBloc, ServiceState>(builder: (_, state) {

              if (state is ServiceOperationFailure) {
                return Text('Could not do Service  operation');
              }
              if (state is ServicesLoadSuccess) {
                final services = state.services;
                return buildContainer(
                    context,
                    ListView.builder(
                        itemCount: services.length,
                        itemBuilder: (ctx, index) => Column(children: [
                              Container(
                                decoration: BoxDecoration(
                                    color: Colors.white38,
                                    borderRadius: BorderRadius.circular(20)),
                                height:
                                    MediaQuery.of(context).size.height * 0.15,
                                margin: EdgeInsets.all(4),
                                padding: EdgeInsets.all(4),
                                child: Card(
                                  elevation: 5,
                                  margin: EdgeInsets.symmetric(
                                      vertical: 8, horizontal: 5),
                                  child: Center(
                                    child: ListTile(
                                      leading: CircleAvatar(
                                        backgroundImage: AssetImage(
                                            'Assets/assets/fixit.png'),
                                      ),
                                      title: Text(
                                        services[index].ServiceName != null
                                            ? services[index].ServiceName
                                            : "place holder",
                                        style: Theme.of(context)
                                            .textTheme
                                            .headline6,
                                      ),
                                      subtitle: Text(
                                        services[index].Description != null
                                            ? services[index].Description
                                            : "Place holder",
                                      ),
                                      trailing: MediaQuery.of(context)
                                                  .size
                                                  .width >
                                              450
                                          ? FlatButton.icon(
                                              textColor:
                                                  Theme.of(context).errorColor,
                                              icon: Icon(
                                                Icons.delete_forever,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            )
                                          : IconButton(
                                              icon: Icon(
                                                Icons.star_border,
                                                color: Theme.of(context)
                                                    .errorColor,
                                              ),
                                            ),
                                      onTap: () => Navigator.of(context)
                                          .pushNamed(ServiceDetail.routeName,
                                              arguments: services[index]),
                                    ),

                                  ),
                                ),
                              )
                            ])));
              }
              return CircularProgressIndicator();
            }),
          ]),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => Navigator.of(context).pushNamed(
            AddUpdateService.routeName,
            arguments: ServiceArgument(edit: false),
          ),
          child: Icon(Icons.add),
        ),

        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
        bottomNavigationBar: BottomAppBar(
          color: Theme.of(context).primaryColor,
          shape: CircularNotchedRectangle(),
          child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                IconButton(
                  icon: Icon(Icons.search),
                  color: Colors.white,
                  onPressed: () {
                    print("search icon button have been clicked");
                  },
                ),
                IconButton(
                  icon: Icon(Icons.note),
                  color: Colors.white,
                  onPressed: () {
                    print("the note icon button have been clicked");
                  },
                )
              ]),
        ));
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
hvenegaspf profile image
hvenegaspf

Great article Thanks!
Is there a link to Part 1?

Collapse
 
yared123yared profile image
Yared Solomon

Thank you very much! check out this link for part one. dev.to/yared123yared/how-can-we-in...

Collapse
 
moaathsaad profile image
MoaathSaad

Is there a GitHub link for the project?

Collapse
 
volokos profile image
volokos

Thank you for article. Can you share service.dart code?