Studi Kasus
Kita akan membuat aplikasi catatan sederhana yang dapat melakukan operasi CRUD. Tampilan aplikasi ini cukup sederhana, dimana kita menggunakan masonry grid view untuk membuat tampilan card yang bertumpuk di setiap baris dengan 2 ukuran yang berbeda. Di bawah ini adalah UI akhir dari tulisan ini.Hasil Akhir |
Membuat Aplikasi Catatan dengan Flutter
Setelah kita membuat back-end di artikel sebelumnya, https://www.sahretech.com/2022/03/crud-flutter-php-mysql-pertama.html. Kali ini kita akan mulai membuat tampilan dan proses aplikasi mobile dengan flutter.1. Buatlah sebuah project flutter baru dengan nama note_app_mysql atau dengan nama bebas.
2. Buka file pubspec.yaml. Lalu tambahkan library flutter_staggered_grid_view: ^0.6.1 dan http: ^0.13.4 kemudian tekan ctrl + s untuk mendownload library yang dibutuhkan.
3. Buka main.dart, lalu edit script di dalamnya dengan script
di bawah ini.
// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors
import 'package:flutter/material.dart';
import 'home.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// themeMode: ThemeMode.dark,
theme: ThemeData(
primaryColor: Colors.black,
scaffoldBackgroundColor: Colors.blueGrey.shade900,
appBarTheme: AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0
)
),
title: 'Flutter + PHP CRUD',
initialRoute: '/',
routes: {
'/': (context) => Home(),
},
);
}
}
4. Buatlah sebuah file baru di dalam folder lib dengan nama
home.dart. Lalu ikuti scriptnya seperti di bawah ini. Jangan lupa
untuk mengganti
http://192.168.100.221 menjadi ip
komputer kalian masing-masing.
// ignore_for_file: prefer_const_constructors, unused_import, prefer_const_literals_to_create_immutables, use_key_in_widget_constructors, prefer_final_fields, unused_field, avoid_print, unnecessary_null_comparison, prefer_is_empty, unused_local_variable
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'add.dart';
import 'edit.dart';
class Home extends StatefulWidget {
@override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
//make list variable to accomodate all data from database
List _get = [];
//make different color to different card
final _lightColors = [
Colors.amber.shade300,
Colors.lightGreen.shade300,
Colors.lightBlue.shade300,
Colors.orange.shade300,
Colors.pinkAccent.shade100,
Colors.tealAccent.shade100
];
@override
void initState() {
super.initState();
//in first time, this method will be executed
_getData();
}
Future _getData() async {
try {
final response = await http.get(Uri.parse(
//you have to take the ip address of your computer.
//because using localhost will cause an error
"http://192.168.1.4/latihan/note_app/list.php"));
// if response successful
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// entry data to variabel list _get
setState(() {
_get = data;
});
}
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Note List'),
),
//if not equal to 0 show data
//else show text "no data available"
body: _get.length != 0
//we use masonry grid to make masonry card style
? MasonryGridView.count(
crossAxisCount: 2,
itemCount: _get.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
//routing into edit page
//we pass the id note
MaterialPageRoute(builder: (context) => Edit(id: _get[index]['id'],)));
},
child: Card(
//make random color to eveery card
color: _lightColors[index % _lightColors.length],
child: Container(
//make 2 different height
constraints:
BoxConstraints(minHeight: (index % 2 + 1) * 85),
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${_get[index]['date']}',
style: TextStyle(color: Colors.black),
),
SizedBox(height: 10),
Text(
'${_get[index]['title']}',
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
);
},
)
: Center(
child: Text(
"No Data Available",
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
child: Icon(Icons.add),
onPressed: () {
Navigator.push(
context,
//routing into add page
MaterialPageRoute(builder: (context) => Add()));
},
),
);
}
}
5. Buatlah sebuah file baru di dalam folder lib dengan nama
add.dart. Lalu ikuti scriptnya seperti di bawah ini. Jangan lupa
untuk mengganti
http://192.168.100.221 menjadi ip
komputer kalian masing-masing.
6. Buatlah sebuah file baru di dalam folder lib dengan nama edit.dart. Lalu ikuti scripnya seperti di bawah ini. Jangan lupa untuk mengganti http://192.168.100.221 menjadi ip komputer kalian masing-masing
// ignore_for_file: unused_field, prefer_const_constructors, avoid_print
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Add extends StatefulWidget {
const Add({Key? key}) : super(key: key);
@override
State<Add> createState() => _AddState();
}
class _AddState extends State<Add> {
final _formKey = GlobalKey<FormState>();
//inisialize field
var title = TextEditingController();
var content = TextEditingController();
Future _onSubmit() async {
try {
return await http.post(
Uri.parse("http://192.168.1.4/latihan/note_app/create.php"),
body: {
"title": title.text,
"content": content.text,
},
).then((value) {
//print message after insert to database
//you can improve this message with alert dialog
var data = jsonDecode(value.body);
print(data["message"]);
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
});
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Create New Note"),
),
body: Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Title',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
TextFormField(
controller: title,
decoration: InputDecoration(
hintText: "Type Note Title",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
fillColor: Colors.white,
filled: true),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
validator: (value) {
if (value!.isEmpty) {
return 'Note Title is Required!';
}
return null;
},
),
SizedBox(height: 20),
Text(
'Content',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
TextFormField(
controller: content,
keyboardType: TextInputType.multiline,
minLines: 5,
maxLines: null,
decoration: InputDecoration(
hintText: 'Type Note Content',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
fillColor: Colors.white,
filled: true),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
validator: (value) {
if (value!.isEmpty) {
return 'Note Content is Required!';
}
return null;
},
),
SizedBox(height: 15),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
child: Text(
"Submit",
style: TextStyle(color: Colors.white),
),
onPressed: () {
//validate
if (_formKey.currentState!.validate()) {
//send data to database with this method
_onSubmit();
}
},
)
],
),
),
),
);
}
}
6. Buatlah sebuah file baru di dalam folder lib dengan nama edit.dart. Lalu ikuti scripnya seperti di bawah ini. Jangan lupa untuk mengganti http://192.168.100.221 menjadi ip komputer kalian masing-masing
// ignore_for_file: unused_field, prefer_const_constructors, avoid_print, use_key_in_widget_constructors, must_be_immutable, unused_element, unused_local_variable, avoid_unnecessary_containers
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Edit extends StatefulWidget {
Edit({required this.id});
String id;
@override
State<Edit> createState() => _EditState();
}
class _EditState extends State<Edit> {
final _formKey = GlobalKey<FormState>();
//inisialize field
var title = TextEditingController();
var content = TextEditingController();
@override
void initState() {
super.initState();
//in first time, this method will be executed
_getData();
}
//Http to get detail data
Future _getData() async {
try {
final response = await http.get(Uri.parse(
//you have to take the ip address of your computer.
//because using localhost will cause an error
//get detail data with id
"http://192.168.1.4/latihan/note_app/detail.php?id='${widget.id}'"));
// if response successful
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() {
title = TextEditingController(text: data['title']);
content = TextEditingController(text: data['content']);
});
}
} catch (e) {
print(e);
}
}
Future _onUpdate(context) async {
try {
return await http.post(
Uri.parse("http://192.168.1.4/latihan/note_app/update.php"),
body: {
"id": widget.id,
"title": title.text,
"content": content.text,
},
).then((value) {
//print message after insert to database
//you can improve this message with alert dialog
var data = jsonDecode(value.body);
print(data["message"]);
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
});
} catch (e) {
print(e);
}
}
Future _onDelete(context) async {
try {
return await http.post(
Uri.parse("http://192.168.1.4/latihan/note_app/delete.php"),
body: {
"id": widget.id,
},
).then((value) {
//print message after insert to database
//you can improve this message with alert dialog
var data = jsonDecode(value.body);
print(data["message"]);
// Remove all existing routes until the home.dart, then rebuild Home.
Navigator.of(context)
.pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
});
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Creat New Note"),
// ignore: prefer_const_literals_to_create_immutables
actions: [
Container(
padding: EdgeInsets.only(right: 20),
child: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
//show dialog to confirm delete data
return AlertDialog(
content: Text('Are you sure you want to delete this?'),
actions: <Widget>[
ElevatedButton(
child: Icon(Icons.cancel),
onPressed: () => Navigator.of(context).pop(),
),
ElevatedButton(
child: Icon(Icons.check_circle),
onPressed: () => _onDelete(context),
),
],
);
},
);
},
icon: Icon(Icons.delete)),
)
],
),
body: Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Title',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
TextFormField(
controller: title,
decoration: InputDecoration(
hintText: "Type Note Title",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
fillColor: Colors.white,
filled: true),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
validator: (value) {
if (value!.isEmpty) {
return 'Note Title is Required!';
}
return null;
},
),
SizedBox(height: 20),
Text(
'Content',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
TextFormField(
controller: content,
keyboardType: TextInputType.multiline,
minLines: 5,
maxLines: null,
decoration: InputDecoration(
hintText: 'Type Note Content',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
fillColor: Colors.white,
filled: true),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
validator: (value) {
if (value!.isEmpty) {
return 'Note Content is Required!';
}
return null;
},
),
SizedBox(height: 15),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
child: Text(
"Submit",
style: TextStyle(color: Colors.white),
),
onPressed: () {
//validate
if (_formKey.currentState!.validate()) {
//send data to database with this method
_onUpdate(context);
}
},
)
],
),
),
),
);
}
}
Semua step sudah kita lalui, dan untuk penjelasannya sudah saya sertakan di dalam script di atas. Jangan lupa juga untuk mengganti setiap ip address 192.168.100.221 menjadi ip address komputer kalian masing-masing. Silahkan bertanya di kolom komentar di bawah ini jika ada kendala. Ok , sekarang waktunya kita lakukan uji coba. Jika semua step di atas berhasil dikerjakan maka tampilannya akan tampak seperti gambar di bawah ini.
Hasil Akhir |
Nah sekarang kalian sudah bisa membuat CRUD dengan menggunakan flutter, php, dan mysql. Selanjutnya kalian bisa melakukan improvisasi dengan menggunakan model dan state management agar aplikasi yang dibangun lebih efisien dan mudah di-maintenance.
Tutorial ini juga bisa kalian kembangkan untuk membuat berbagai macam aplikasi yang membuatuhkan crud dasar seperti aplikasi pendataan, kontak, penilaian dan semacamnya.
Ok, sekian tutorial kita kali ini tentang Tutorial CRUD Flutter PHP dan MySQL Part Kedua. Semoga bermanfaat, jika ada kendala silahkan tanya langsung di kolom komentar atau bertanya langsung di fanspage sahretech. Sekian dan terima kasih.
good article
ReplyDeletecara runnning nya gimana bang?
ReplyDeleteHidupin emulator, trus tekan F5. Klo belum ada emulator bisa pake chrome, atau hp beneran.
DeleteBang nanya, sudah bisa run app. jalan tapi muncul error XMLHttpRequest error. solusinya gimana ya udah cari - cari masih gak nemu
ReplyDeletecoba tambah script ini mas di setelah url endpoint
Deleteheaders: {
"Accept": "application/json",
"Access-Control_Allow_Origin": "*"
});
yang ini penempatanya bagian mana ya mas? punya saya jalan tapi ada error seperti komentar yang ini
Deletesearchnya ngga ada ?
ReplyDeleteiya mas gk ada, ini fokusnya di crud. klo pencarian ada contohnya di tutorial fluuter saya yang ini https://www.sahretech.com/2022/02/cara-mudah-membuat-fitur-pencarian-flutter.html
Deletegan itu flutter nya versi berapa ya...
ReplyDeletekok saya no data available ya? padahal kalo dipanggil langsung urlnya dah muncul json nya
ReplyDeletedicopas aja mas biar gk salah, klo pake real device pastiin satu jaringan dengan komputer yang digunakan atau coba tampilin dulu data jsonnya di log
DeleteSudah saya coba dan berhasil mas, tetapi hanya bisa input satu data saja, tidak bisa dua data atau lebih, apakah memang seperti itu mas?
ReplyDeletelihat lognya mas apakah terdapat error atau tidak
DeletePoin 1 di part kedua file ny di letakan dalam folder mana...?
ReplyDeleteSemua file ditarok di folder lib
Deletebg mau tnya, knp gk bsa pas saat munculin list dan create data lwat aplikasi?sdangkan lwat postman baik2 saja
ReplyDeleteTerimakasih tutornya, dah saya coba run semua lancar, kecuali pas edit, data sebelumnya tidak tampil, dan di terminal ada notif "FormatException: Unexpected character (at character 1)" itu kenapa yah bg? cuma untuk update prosesnya sih jalan
ReplyDeletebang kenpa yg punya saya gabisa nge submitt
ReplyDeleteNotif di debug consoleny ape bang?
Deleteuntuk run debug pilikan pake windows normal, pake crome masih no data available kenap ya min
ReplyDelete