Flutter
DIO

Hari Ke-4

rest api










Struktur Folder

pubspec.yaml
cupertino_icons: ^0.1.2
fluttertoast: ^3.1.3
dio: ^3.0.8
image_picker: ^0.6.3+1core/config/endpoint.dart
class Endpoint {
static String _baseURL = "https://leeyurani.com/restofood/public/api";
static String baseFoods = "${_baseURL}/foods";
}core/models/foods_mdl.dart
import 'dart:io';
import 'package:dio/dio.dart';
class FoodModel {
String id;
String title;
String description;
String fullDescription;
int price;
String image;
MultipartFile imageFile;
FoodModel({
this.id, this.title, this.description,
this.fullDescription, this.price, this.image,
this.imageFile
});
//Converter dari map ke object
factory FoodModel.fromJson(Map<String, dynamic> json) {
return FoodModel(
id: json['id'].toString(),
title: json['title'],
description: json['description'],
fullDescription: json['full_description'],
price: int.parse(json['price'].toString()),
image: json['image']
);
}
//Converter dari object ke map
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
if (id != null) {
map['id'] = id;
}
map['title'] = title;
map['description'] = description;
map['full_description'] = fullDescription;
map['price'] = price;
map['image'] = imageFile;
return map;
}
}
class FoodResponse {
int status;
String message;
FoodResponse({this.status, this.message});
factory FoodResponse.fromJson(Map<String, dynamic> json) {
return FoodResponse(
status: int.parse(json['status'].toString()),
message: json['message']
);
}
}core/services/food_services.dart
import 'package:restofood_api/core/config/endpoint.dart';
import 'package:restofood_api/core/models/foods_mdl.dart';
import 'package:dio/dio.dart';
class FoodServices {
static Dio dio = new Dio();
//Get foods data
static Future<List<FoodModel>> getAll() async {
var response = await dio.get(
Endpoint.baseFoods,
options: Options(
headers: {
"Accept": "application/json"
}
)
);
var _foodData = List<FoodModel>();
response.data["data"].forEach((value) {
_foodData.add(FoodModel.fromJson(value));
});
return _foodData;
}
static Future<FoodResponse> createFood(FoodModel foodModel) async {
var response = await dio.post(
Endpoint.baseFoods,
data: FormData.fromMap(foodModel.toMap()),
options: Options(
headers: {
"Accept": "application/json"
}
)
);
return FoodResponse.fromJson(response.data);
}
static Future<FoodResponse> updateFood(FoodModel foodModel, String id) async {
//Untuk method put pemanggilannya
//harus ditambahkan _method dan method typenya POST
var foodData = foodModel.toMap();
foodData['_method'] = "PUT";
var response = await dio.post(
Endpoint.baseFoods + "/${id}",
data: FormData.fromMap(foodData),
options: Options(
headers: {
"Accept": "application/json",
}
)
);
return FoodResponse.fromJson(response.data);
}
static Future<FoodResponse> deleteFood(String id) async {
var response = await dio.delete(
Endpoint.baseFoods + '/${id}',
options: Options(
headers: {
"Accept": "application/json"
}
)
);
return FoodResponse.fromJson(response.data);
}
}core/utils/toast_utils.dart
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ToastUtils {
static void show(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.black87,
textColor: Colors.white,
fontSize: 16.0
);
}
}ui/widgets/custom_textfield.dart
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
TextInputAction action;
TextInputType type;
TextEditingController controller;
String hintText;
CustomTextField({
this.action, this.type,
this.controller, this.hintText
});
@override
Widget build(BuildContext context) {
return Container(
child: TextField(
textInputAction: action,
keyboardType: type,
controller: controller,
decoration: InputDecoration(
hintText: hintText
),
),
);
}
}ui/screens/add_screen.dart
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:restofood_api/core/models/foods_mdl.dart';
import 'package:restofood_api/core/services/food_services.dart';
import 'package:restofood_api/core/utils/toast_utils.dart';
import 'package:restofood_api/ui/widgets/custom_textfield.dart';
class AddScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orange,
title: Text("Add Food", style: TextStyle(color: Colors.white)),
),
body: AddBody(),
);
}
}
class AddBody extends StatefulWidget {
@override
_AddBodyState createState() => _AddBodyState();
}
class _AddBodyState extends State<AddBody> {
File image;
var titleController = TextEditingController();
var descriptionController = TextEditingController();
var fullDescriptionController = TextEditingController();
var priceController = TextEditingController();
void imagePick() async {
var _image = await ImagePicker.pickImage(source: ImageSource.gallery);
if (_image != null) {
setState(() {
image = _image;
});
}
}
void createFoods() async {
if (titleController.text.isNotEmpty && descriptionController.text.isNotEmpty
&& fullDescriptionController.text.isNotEmpty && priceController.text.isNotEmpty
&& image != null
) {
var foodData = FoodModel(
title: titleController.text,
description: descriptionController.text,
fullDescription: fullDescriptionController.text,
price: int.parse(priceController.text),
imageFile: await MultipartFile.fromFile(image.path)
);
ToastUtils.show("Membuat makanan");
FoodResponse response = await FoodServices.createFood(foodData);
if (response.status == 200) {
ToastUtils.show(response.message);
Future.delayed(Duration(
seconds: 1
), () {
Navigator.pushNamedAndRemoveUntil(context, "/home", (Route<dynamic> routes) => false);
});
} else {
ToastUtils.show(response.message);
}
} else {
ToastUtils.show("Silahkan isi semua field");
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Container(
child: InkWell(
onTap: () => imagePick(),
child: image == null ?
Icon(Icons.add_photo_alternate, color: Colors.orange, size: 100,) :
Image.file(image, width: 100, height: 100,),
),
),
),
SizedBox(height: 20),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.text,
controller: titleController,
hintText: "Nama Makanan",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.text,
controller: descriptionController,
hintText: "Deskripsi",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.multiline,
controller: fullDescriptionController,
hintText: "Full Deskripsi",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.number,
controller: priceController,
hintText: "Harga",
),
Container(
margin: EdgeInsets.only(top: 20),
height: 40,
width: MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => createFoods(),
color: Colors.orange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)
),
child: Text(
"Tambah Makanan",
style: TextStyle(
color: Colors.white
),
),
),
)
],
),
);
}
}
ui/screens/detail_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:restofood_api/core/models/foods_mdl.dart';
import 'package:restofood_api/core/services/food_services.dart';
import 'package:restofood_api/core/utils/toast_utils.dart';
import 'package:restofood_api/ui/screens/update_screen.dart';
class DetailScreen extends StatelessWidget {
FoodModel foodModel;
DetailScreen({this.foodModel});
void deleteFoods(BuildContext context) async {
FoodResponse response = await FoodServices.deleteFood(foodModel.id);
if (response.status == 200) {
ToastUtils.show(response.message);
Future.delayed(Duration(
seconds: 1
), () {
Navigator.pushNamedAndRemoveUntil(context, "/home", (Route<dynamic> routes) => false);
});
} else {
ToastUtils.show(response.message);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orange,
title: Text(foodModel.title, style: TextStyle(color: Colors.white)),
actions: <Widget>[
Padding(
padding: EdgeInsets.only(right: 20),
child: InkWell(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (context) => UpdateScreen(
foodModel: foodModel,
)
)),
child: Icon(Icons.edit, color: Colors.white,)
),
),
Padding(
padding: EdgeInsets.only(right: 20),
child: InkWell(
onTap: () => deleteFoods(context),
child: Icon(Icons.delete, color: Colors.white,)
),
),
],
),
body: DetailBody(
foodModel: foodModel,
),
);
}
}
class DetailBody extends StatelessWidget {
FoodModel foodModel;
DetailBody({this.foodModel});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
//bagian untuk meload gambar
new Stack(
children: <Widget>[
new ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0)),
child: Image.network(
foodModel.image,
width: double.infinity,
height: MediaQuery.of(context).size.width / 2,
fit: BoxFit.cover,
),
),
new Positioned(
bottom: 20,
right: 15,
child: Container(
width: 300,
color: Colors.red,
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text(
foodModel.title,
style: TextStyle(fontSize: 26, color: Colors.white),
),
Text(
"Harga: Rp ${foodModel.price}", style: TextStyle(fontSize: 15, color: Colors.grey),
softWrap: true,
overflow: TextOverflow.fade,
)
],
),
),
)
],
),
new SizedBox(height: 4,),
new Card(
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Row(
children: <Widget>[
new Icon(Icons.assignment, size: 20),
Padding(
padding: const EdgeInsets.only(left: 10),
child: new Text(
"Description", style: TextStyle(fontSize: 20, ),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: new Text(foodModel.fullDescription, style: TextStyle(fontSize: 15,),),
)
],
),
)
],
),
);
}
}
ui/screens/home_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:restofood_api/core/models/foods_mdl.dart';
import 'package:restofood_api/core/services/food_services.dart';
import 'package:restofood_api/ui/screens/add_screen.dart';
import 'package:restofood_api/ui/screens/detail_screen.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orange,
title: Text("Restofood", style: TextStyle(color: Colors.white)),
leading: Icon(Icons.fastfood, color: Colors.white,),
actions: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10),
child: InkWell(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (context) => AddScreen()
)),
child: Icon(Icons.add_circle, color: Colors.white,)
),
)
],
),
body: HomeBody(),
);
}
}
class HomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//Bagian ini untuk itemnya
Text(
"Daftar Makanan & Minuman",
style: TextStyle(
fontSize: 18,
color: Colors.black87,
fontWeight: FontWeight.bold
),
),
SizedBox(height: 20),
//Widget daftar makanan
ListFood()
],
),
);
}
}
class ListFood extends StatefulWidget {
@override
_ListFoodState createState() => _ListFoodState();
}
class _ListFoodState extends State<ListFood> {
//Instance data foods
List<FoodModel> foods;
void loadData() async {
var _foods = foods = await FoodServices.getAll();
setState(() {
foods = _foods;
});
}
@override
void initState() {
super.initState();
this.loadData();
}
@override
Widget build(BuildContext context) {
if (foods == null) {
return Center(
child: CircularProgressIndicator(
),
);
}
return Container(
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: foods.length,
itemBuilder: (context, index) {
//Menambahkan item list
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Card(
elevation: 1,
child: InkWell(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (context) => DetailScreen(
foodModel: foods[index],
)
)),
child: Container(
padding: EdgeInsets.all(10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//Bagian ini tambahkan image
Container(
width: 64,
height: 64,
child: Image.network(
foods[index].image,
fit: BoxFit.cover,
),
),
//Memberi jarak
SizedBox(width: 10),
//Bagian untuk title dan description
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//Title
Text(
foods[index].title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold
),
),
//Memberi jarak
SizedBox(height: 5,),
//Description
Container(
width: MediaQuery.of(context).size.width * 0.5,
child: Text(
foods[index].description,
style: TextStyle(
fontSize: 14,
color: Colors.black54
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
//Harga
Container(
width: MediaQuery.of(context).size.width * 0.6,
child: Align(
alignment: Alignment.topRight,
child: Text(
"Rp ${foods[index].price.toString()}",
style: TextStyle(
fontSize: 14,
color: Colors.green,
fontWeight: FontWeight.bold
),
),
),
),
],
)
],
),
),
),
),
);
},
)
);
}
}
ui/screens/update_screen.dart
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:restofood_api/core/models/foods_mdl.dart';
import 'package:restofood_api/core/services/food_services.dart';
import 'package:restofood_api/core/utils/toast_utils.dart';
import 'package:restofood_api/ui/widgets/custom_textfield.dart';
class UpdateScreen extends StatelessWidget {
FoodModel foodModel;
UpdateScreen({this.foodModel});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orange,
title: Text("Update Food", style: TextStyle(color: Colors.white)),
),
body: UpdateFood(
foodModel: foodModel,
),
);
}
}
class UpdateFood extends StatefulWidget {
FoodModel foodModel;
UpdateFood({this.foodModel});
@override
_UpdateFoodState createState() => _UpdateFoodState();
}
class _UpdateFoodState extends State<UpdateFood> {
File image;
var titleController = TextEditingController();
var descriptionController = TextEditingController();
var fullDescriptionController = TextEditingController();
var priceController = TextEditingController();
void imagePick() async {
var _image = await ImagePicker.pickImage(source: ImageSource.gallery);
if (_image != null) {
setState(() {
image = _image;
});
}
}
void updateFoods() async {
if (titleController.text.isNotEmpty && descriptionController.text.isNotEmpty
&& fullDescriptionController.text.isNotEmpty && priceController.text.isNotEmpty
&& image != null
) {
var foodData = FoodModel(
title: titleController.text,
description: descriptionController.text,
fullDescription: fullDescriptionController.text,
price: int.parse(priceController.text),
imageFile: await MultipartFile.fromFile(image.path)
);
FoodResponse response = await FoodServices.updateFood(foodData, widget.foodModel.id);
if (response.status == 200) {
ToastUtils.show(response.message);
Navigator.pushNamedAndRemoveUntil(context, "/home", (Route<dynamic> routes) => false);
} else {
ToastUtils.show(response.message);
}
} else {
ToastUtils.show("Silahkan isi semua field");
}
}
void loadFood() {
setState(() {
titleController.text = widget.foodModel.title;
descriptionController.text = widget.foodModel.description;
fullDescriptionController.text = widget.foodModel.fullDescription;
priceController.text = widget.foodModel.price.toString();
});
}
@override
void initState() {
super.initState();
this.loadFood();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Container(
child: InkWell(
onTap: () => imagePick(),
child: image == null ?
Icon(Icons.add_photo_alternate, color: Colors.orange, size: 100,) :
Image.file(image, width: 100, height: 100,),
),
),
),
SizedBox(height: 20),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.text,
controller: titleController,
hintText: "Nama Makanan",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.text,
controller: descriptionController,
hintText: "Deskripsi",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.multiline,
controller: fullDescriptionController,
hintText: "Full Deskripsi",
),
SizedBox(height: 10),
CustomTextField(
action: TextInputAction.done,
type: TextInputType.number,
controller: priceController,
hintText: "Harga",
),
Container(
margin: EdgeInsets.only(top: 20),
height: 40,
width: MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => updateFoods(),
color: Colors.orange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)
),
child: Text(
"Update Makanan",
style: TextStyle(
color: Colors.white
),
),
),
)
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:restofood_api/ui/screens/home_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Restofood",
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.orange,
accentColor: Colors.orange
),
home: HomeScreen(),
routes: {
"/home": (context) => HomeScreen()
},
);
}
}main.dart

10 Hari Jago Flutter - Hari 4
By flutter id
10 Hari Jago Flutter - Hari 4
10 Hari Jago Flutter - Hari 4
- 789