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+1

core/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