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.
and this is the service add and update page.
and this one is the service detail page on this page we can delete and edit the single service.
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.
Then you will get the following page.
Click on the Flutter Application and click on the Next button
then here give the application a new name. and click on the Next button.
Then click on the Finish button at the bottom.
here we get a default code.
First, we should prepare a debugging emulator or connect our phone. and it will come at the top.
after that click on the green run button at the top. you will get this page on the emulator.
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.
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';
Bloc Architecture
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.
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;
create a class called ServiceDataProvider
class ServiceDataProvider {
}
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';
Then define the httpClient
final http.Client httpClient;
next, add the constructor for the class.and inside the constructor specify the necessary parameter .
ServiceDataProvider({@required this.httpClient}) : assert(httpClient != null);
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 {
}
then let us define the response
final response = await httpClient.post(
)
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'),
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',
},
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,
}),
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,
}),
);
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.');
}
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.');
}
}
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");
}
}
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.');
}
}
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.');
}
}
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.');
}
}
}
data_provider.dart
this will export the Service_data add the following line of code inside.
export 'Service_data.dart';
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.
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);
}
}
The repository.dart file does nothing but exporting the Service_repository.dart.
export 'Service_repository.dart';
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.
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 {}
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}';
}
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();
}
}
}
}
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';
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.
The main page of the app looks like the following.
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';
Create a class called ServiceMainScreen and define the class routeName as Static.
class ServiceMainScreen extends StatelessWidget {
static const routeName='/category_screen';
}
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,),
);
}
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,
);
}
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
}
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(
//
)
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.
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
This is the main portion of the application, it will cover the place other than the bottomNavigationBar and the floatingActionButton
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
]
)
Then inside the children widget we have the following childrent respectively.
* Image Section
* Text Section
* List View
Image Section
Container(
height: MediaQuery.of(context).size.width * 0.5,
width: double.infinity,
child: Image.asset("Assets/assets/wood.jpg"),
),
Text Section
buildSectionTitle(context, 'Services'),
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.
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
}
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');
}
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]),
),
),
),
)
])));
}
then on the defualt position let us return a progress indicator.
return CircularProgressIndicator();
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();
}),
So by this we have finalized the body.
floatingActionButton
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),
),
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,
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.
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");
},
)
]),
)
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");
},
)
]),
));
}
}
Top comments (4)
Great article Thanks!
Is there a link to Part 1?
Thank you very much! check out this link for part one. dev.to/yared123yared/how-can-we-in...
Is there a GitHub link for the project?
Thank you for article. Can you share service.dart code?