κ²μν μ±
β’
νλ‘μ νΈ κ΅¬μ‘°
β¦
ν΄λΌμ΄μΈνΈ
β¦
μλ²
β’
μ½λ
β¦
board.dart
β¦
main.dart
β¦
main_screen.dart
β¦
list_screen.dart
β¦
read_screen.dart
β¦
insert_screen.dart
β¦
update_screen.dart
νλ‘μ νΈ κ΅¬μ‘°
β’
ν΄λΌμ΄μΈνΈ
β’
μλ²
ν΄λΌμ΄μΈνΈ
Flutter - board_app
π board_app
βββ π android
βββ π ios
βββ π build
βββ π lib
β βββ π models
β β βββ π board.dart
β βββ π screen
β β βββ π board
β β β βββ π insert_screen.dart
β β β βββ π list_screen.dart
β β β βββ π read_screen.dart
β β β βββ π update_screen.dart
β β βββ π main_screen.dart
β βββ π main.dart
βββ π test
βββ π pubspec.yaml
βββ π pubspec.lock
βββ π README.md
Dart
볡μ¬
μλ²
SpringBoot - board
board.war
μλ² μ€ννκΈ°
java -jar board.war
Bash
볡μ¬
μ½λ
β’
board.dart
β’
main.dart
β’
main_screen.dart
β’
list_screen.dart
β’
read_screen.dart
β’
insert_screen.dart
β’
update_screen.dart
board.dart
class Board {
int? no;
String? title;
String? writer;
String? content;
DateTime? regDate;
DateTime? updDate;
Board({
required this.no,
required this.title,
required this.writer,
required this.content,
this.regDate,
this.updDate
});
}
Dart
볡μ¬
main.dart
import 'package:flutter/material.dart';
import 'package:http_app/main_screen.dart';
import 'package:http_app/screen/board/insert_screen.dart';
import 'package:http_app/screen/board/list_screen.dart';
import 'package:http_app/screen/board/read_screen.dart';
import 'package:http_app/screen/board/update_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
initialRoute: '/main',
routes: {
'/main' : (context) => const MainScreen(),
'/board/list' : (context) => const ListScreen(),
'/board/read' : (context) => const ReadScreen(),
'/board/insert' : (context) => const InsertScreen(),
'/board/update' : (context) => const UpdateScreen(),
},
);
}
}
Dart
볡μ¬
main_screen.dart
import 'package:flutter/material.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("λ©μΈ νλ©΄"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, "/board/list");
},
child: Text("κ²μκΈ λͺ©λ‘"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
),
),
),
);
}
}
Dart
볡μ¬
list_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http_app/models/board.dart';
import 'package:http/http.dart' as http;
class ListScreen extends StatefulWidget {
const ListScreen({super.key});
State<ListScreen> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
List<Board> _boardList = [];
void initState() {
super.initState();
getBoardList().then((result) {
setState(() {
_boardList = result;
});
});
}
// π κ²μκΈ λͺ©λ‘ λ°μ΄ν° μμ²
Future<List<Board>> getBoardList() async {
var url = "http://10.0.2.2:8080/board";
List<Board> list = [];
try {
var response = await http.get(Uri.parse(url));
print("::::: response - body :::::");
print(response.body);
// UTF-8 λμ½λ©
var utf8Decoded = utf8.decode(response.bodyBytes);
// JSON λμ½λ©
var boardList = jsonDecode(utf8Decoded);
for (var i = 0; i < boardList.length; i++) {
list.add(Board(
no: boardList[i]['no'],
title: boardList[i]['title'],
writer: boardList[i]['writer'],
content: boardList[i]['content'],
));
}
print(list);
} catch (e) {
print(e);
}
return list;
}
final List<PopupMenuEntry<String>> _popupMenuItems = [
const PopupMenuItem<String>(
value: 'update',
child: Row(
children: [
Icon(Icons.edit, color: Colors.black), // μμ΄μ½
SizedBox(width: 8), // μμ΄μ½κ³Ό ν
μ€νΈ μ¬μ΄μ κ°κ²© μΆκ°
Text('μμ νκΈ°'), // ν
μ€νΈ
],
),
),
const PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Colors.black), // μμ΄μ½
SizedBox(width: 8), // μμ΄μ½κ³Ό ν
μ€νΈ μ¬μ΄μ κ°κ²© μΆκ°
Text('μμ νκΈ°'), // ν
μ€νΈ
],
),
),
];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("κ²μκΈ λͺ©λ‘")),
body: Container(
padding: const EdgeInsets.fromLTRB(5, 0, 5, 10),
child: ListView.builder(
itemCount: _boardList.length,
itemBuilder: (context, index) {
return GestureDetector(
child: Card(
child: ListTile(
leading: Text(_boardList[index].no.toString() ?? '0'),
title: Text(_boardList[index].title ?? "μ λͺ©μμ"),
subtitle: Text(_boardList[index].writer ?? '-'),
trailing: PopupMenuButton<String>(
itemBuilder: (BuildContext context) {
return _popupMenuItems;
},
onSelected: (String value) async {
if (value == 'update') {
Navigator.pushNamed(
context,
"/board/update",
arguments: _boardList[index].no,
);
} else if (value == 'delete') {
bool check = await _showDeleteConfirmDialog();
if (check) {
deleteBoard(_boardList[index].no).then((result) {
if (result) {
setState(() {
_boardList.removeAt(index);
});
}
});
}
}
},
),
),
),
onTap: () {
Navigator.pushNamed(
context,
"/board/read",
arguments: _boardList[index].no,
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushReplacementNamed(context, "/board/insert");
},
child: const Icon(Icons.create),
),
);
}
/// κ²μκΈ μμ μμ²
Future<bool> deleteBoard(int? no) async {
var url = "http://10.0.2.2:8080/board/$no";
try {
var response = await http.delete(Uri.parse(url));
print("::::: response - statusCode :::::");
print(response.statusCode);
if (response.statusCode == 200 || response.statusCode == 204) {
// μ±κ³΅μ μΌλ‘ μμ λ¨
print("κ²μκΈ μμ μ±κ³΅");
return true;
} else {
// μ€ν¨ μ μ€λ₯ λ©μμ§
throw Exception('Failed to delete board. Status code: ${response.statusCode}');
}
} catch (e) {
print(e);
return false;
}
}
/// μμ νμΈ λ€μ΄μΌλ‘κ·Έ νμ
Future<bool> _showDeleteConfirmDialog() async {
bool result = false;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('μμ νμΈ'),
content: const Text('μ λ§λ‘ μ΄ κ²μκΈμ μμ νμκ² μ΅λκΉ?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false); // μ·¨μλ₯Ό ν΄λ¦νλ©΄ false λ°ν
},
child: const Text('μ·¨μ'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true); // μμ λ₯Ό ν΄λ¦νλ©΄ true λ°ν
},
child: const Text('μμ '),
),
],
);
},
).then((value) {
result = value ?? false;
});
return result;
}
}
Dart
볡μ¬
read_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http_app/models/board.dart';
class ReadScreen extends StatefulWidget {
const ReadScreen({super.key});
State<ReadScreen> createState() => _ReadScreenState();
}
class _ReadScreenState extends State<ReadScreen> {
late int no;
late Future<Board> _board;
final List<PopupMenuEntry<String>> _popupMenuItems = [
const PopupMenuItem<String>(
value: 'update',
child: Row(
children: [
Icon(Icons.edit, color: Colors.black), // μμ΄μ½
SizedBox(width: 8), // μμ΄μ½κ³Ό ν
μ€νΈ μ¬μ΄μ κ°κ²© μΆκ°
Text('μμ νκΈ°'), // ν
μ€νΈ
],
),
),
const PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Colors.black), // μμ΄μ½
SizedBox(width: 8), // μμ΄μ½κ³Ό ν
μ€νΈ μ¬μ΄μ κ°κ²© μΆκ°
Text('μμ νκΈ°'), // ν
μ€νΈ
],
),
),
];
void didChangeDependencies() {
super.didChangeDependencies();
final arguments = ModalRoute.of(context)!.settings.arguments;
if (arguments != null) {
no = arguments as int;
_board = getBoard(no);
} else {
// κΈ°λ³Έκ° μ€μ λλ μμΈ μ²λ¦¬
no = 0;
_board = Future.error('No board number provided');
}
}
///
/// π©βπ» κ²μκΈ μ‘°ν μμ²
///
Future<Board> getBoard(int no) async {
var url = "http://10.0.2.2:8080/board/$no";
try {
var response = await http.get(Uri.parse(url));
print("::::: response - body :::::");
print(response.body);
// UTF-8 λμ½λ©
var utf8Decoded = utf8.decode(response.bodyBytes);
// JSON λμ½λ©
var boardJson = jsonDecode(utf8Decoded);
print(boardJson);
return Board(
no: boardJson['no'],
title: boardJson['title'],
writer: boardJson['writer'],
content: boardJson['content'],
);
} catch (e) {
print(e);
throw Exception('Failed to load board');
}
}
/// κ²μκΈ μμ μμ²
Future<bool> deleteBoard(int no) async {
var url = "http://10.0.2.2:8080/board/$no";
try {
var response = await http.delete(Uri.parse(url));
print("::::: response - statusCode :::::");
print(response.statusCode);
if (response.statusCode == 200 || response.statusCode == 204) {
// μ±κ³΅μ μΌλ‘ μμ λ¨
print("κ²μκΈ μμ μ±κ³΅");
return true;
} else {
// μ€ν¨ μ μ€λ₯ λ©μμ§
throw Exception('Failed to delete board. Status code: ${response.statusCode}');
}
} catch (e) {
print(e);
return false;
}
}
///
/// β μμ νμΈ
///
Future<bool> _showDeleteConfirmDialog() async {
bool result = false;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('μμ νμΈ'),
content: Text('μ λ§λ‘ μ΄ κ²μκΈμ μμ νμκ² μ΅λκΉ?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false); // μ·¨μλ₯Ό ν΄λ¦νλ©΄ false λ°ν
},
child: Text('μ·¨μ'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true); // μμ λ₯Ό ν΄λ¦νλ©΄ true λ°ν
},
child: Text('μμ '),
),
],
);
},
).then((value) {
// λ€μ΄μΌλ‘κ·Έ κ²°κ³Όλ₯Ό resultμ μ μ₯
result = value ?? false;
});
return result;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("κ²μκΈ μ‘°ν"),
actions: [
PopupMenuButton(
itemBuilder: (BuildContext context) {
return _popupMenuItems;
},
icon: const Icon(Icons.more_vert),
onSelected: (String value) async {
if (value == 'update') {
Navigator.pushReplacementNamed(context, "/board/update", arguments: no,);
} else if (value == 'delete') {
bool check = await _showDeleteConfirmDialog();
if( check ) {
deleteBoard(no).then((result) {
if( result ) {
Navigator.pop(context);
Navigator.pushReplacementNamed(context, "/board/list");
}
});
}
}
},
)
],
),
body: FutureBuilder<Board>(
future: _board,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData) {
return const Center(child: Text('No data found'));
} else {
var board = snapshot.data!;
return
Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 5, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: ListTile(
leading: const Icon(Icons.article),
title: Text(board.title ?? 'μ λͺ©'),
),
),
Card(
child: ListTile(
leading: const Icon(Icons.person),
title: Text(board.writer ?? 'μμ±μ'),
),
),
const SizedBox(height: 10.0,),
Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
padding: const EdgeInsets.all(12.0),
width: double.infinity,
height: 320.0,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3), // κ·Έλ¦Όμμ μμκ³Ό ν¬λͺ
λ
spreadRadius: 2, // κ·Έλ¦Όμμ νμ° μ λ
blurRadius: 8, // κ·Έλ¦Όμμ νλ¦Ό μ λ
offset: const Offset(4, 4), // κ·Έλ¦Όμμ μμΉ (x, y)
),
],
borderRadius: BorderRadius.circular(8), // μ΅μ
: λͺ¨μ리 λ₯κΈκΈ°
),
child: SingleChildScrollView(
child: Text(board.content ?? 'λ΄μ©')
)
),
]
),
);
}
},
),
);
}
}
Dart
볡μ¬
insert_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class InsertScreen extends StatefulWidget {
const InsertScreen({super.key});
State<InsertScreen> createState() => _InsertScreenState();
}
class _InsertScreenState extends State<InsertScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _titleController = TextEditingController();
final TextEditingController _writerController = TextEditingController();
final TextEditingController _contentController = TextEditingController();
Future<void> insert() async {
if (_formKey.currentState!.validate()) {
var url = "http://10.0.2.2:8080/board";
try {
var response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
'title': _titleController.text,
'writer': _writerController.text,
'content': _contentController.text,
}),
);
print("::::: response - body :::::");
print(response.body);
if (response.statusCode == 200 || response.statusCode == 201) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('κ²μκΈ λ±λ‘ μ±κ³΅!'),
backgroundColor: Colors.blueAccent,
),
);
Navigator.pushReplacementNamed(context, "/board/list");
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('κ²μκΈ λ±λ‘ μ€ν¨...'),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
print(e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('μλ¬: $e')),
);
}
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("κ²μκΈ μμ±"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _titleController,
decoration: const InputDecoration(labelText: 'μ λͺ©'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'μ λͺ©μ μ
λ ₯νμΈμ';
}
return null;
},
),
TextFormField(
controller: _writerController,
decoration: const InputDecoration(labelText: 'μμ±μ'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'μμ±μλ₯Ό μ
λ ₯νμΈμ';
}
return null;
},
),
TextFormField(
controller: _contentController,
decoration: const InputDecoration(labelText: 'λ΄μ©'),
maxLines: 5,
validator: (value) {
if (value == null || value.isEmpty) {
return 'λ΄μ©μ μ
λ ₯νμΈμ';
}
return null;
},
),
],
),
),
),
bottomSheet: Container(
height: 60,
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () {
insert();
},
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50), // κ°λ‘ 100% λ²νΌ
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero, // ν
λ리λ₯Ό λ₯κΈμ§ μκ² μ€μ
),
),
child: const Text('λ±λ‘νκΈ°'),
),
),
),
);
}
}
Dart
볡μ¬
update_screen.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class UpdateScreen extends StatefulWidget {
const UpdateScreen({super.key});
State<UpdateScreen> createState() => _UpdateScreenState();
}
class _UpdateScreenState extends State<UpdateScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _titleController = TextEditingController();
final TextEditingController _writerController = TextEditingController();
final TextEditingController _contentController = TextEditingController();
late int no;
final List<PopupMenuEntry<String>> _popupMenuItems = [
const PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Colors.black), // μμ΄μ½
SizedBox(width: 8), // μμ΄μ½κ³Ό ν
μ€νΈ μ¬μ΄μ κ°κ²© μΆκ°
Text('μμ νκΈ°'), // ν
μ€νΈ
],
),
),
];
void didChangeDependencies() {
super.didChangeDependencies();
final arguments = ModalRoute.of(context)!.settings.arguments;
if (arguments != null) {
no = arguments as int;
getBoard(no); // μ΄λ¦ λ³κ²½λ λ©μλ νΈμΆ
}
}
///
/// π©βπ» κ²μκΈ μ‘°ν μμ²
///
Future<void> getBoard(int no) async {
var url = "http://10.0.2.2:8080/board/$no";
try {
var response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
var utf8Decoded = utf8.decode(response.bodyBytes);
var boardJson = jsonDecode(utf8Decoded);
_titleController.text = boardJson['title'];
_writerController.text = boardJson['writer'];
_contentController.text = boardJson['content'];
} else {
throw Exception('Failed to load board details');
}
} catch (e) {
print(e);
}
}
/// κ²μκΈ μμ μμ²
Future<void> updateBoard() async {
if (_formKey.currentState!.validate()) {
var url = "http://10.0.2.2:8080/board";
try {
var response = await http.put(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
'no' : no,
'title': _titleController.text,
'writer': _writerController.text,
'content': _contentController.text,
}),
);
if (response.statusCode == 200 || response.statusCode == 204) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('κ²μκΈ μμ μ±κ³΅!'),
backgroundColor: Colors.blueAccent,
),
);
Navigator.pushReplacementNamed(context, "/board/list");
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('κ²μκΈ μμ μ€ν¨...'),
backgroundColor: Colors.redAccent,
),
);
}
} catch (e) {
print(e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('μλ¬: $e')),
);
}
}
}
/// κ²μκΈ μμ μμ²
Future<bool> deleteBoard(int no) async {
var url = "http://10.0.2.2:8080/board/$no";
try {
var response = await http.delete(Uri.parse(url));
print("::::: response - statusCode :::::");
print(response.statusCode);
if (response.statusCode == 200 || response.statusCode == 204) {
// μ±κ³΅μ μΌλ‘ μμ λ¨
print("κ²μκΈ μμ μ±κ³΅");
return true;
} else {
// μ€ν¨ μ μ€λ₯ λ©μμ§
return false;
}
} catch (e) {
print(e);
return false;
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("κ²μκΈ μμ "),
actions: [
PopupMenuButton(
itemBuilder: (BuildContext context) {
return _popupMenuItems;
},
icon: const Icon(Icons.more_vert),
onSelected: (String value) async {
if (value == 'delete') {
bool check = await _showDeleteConfirmDialog();
if( check ) {
deleteBoard(no).then((result) {
if( result ) {
Navigator.pop(context);
Navigator.pushReplacementNamed(context, "/board/list");
}
});
}
}
},
)
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _titleController,
decoration: const InputDecoration(labelText: 'μ λͺ©'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'μ λͺ©μ μ
λ ₯νμΈμ';
}
return null;
},
),
TextFormField(
controller: _writerController,
decoration: const InputDecoration(labelText: 'μμ±μ'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'μμ±μλ₯Ό μ
λ ₯νμΈμ';
}
return null;
},
),
TextFormField(
controller: _contentController,
decoration: const InputDecoration(labelText: 'λ΄μ©'),
maxLines: 5,
validator: (value) {
if (value == null || value.isEmpty) {
return 'λ΄μ©μ μ
λ ₯νμΈμ';
}
return null;
},
),
],
),
),
),
bottomSheet: Container(
height: 60,
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () {
updateBoard();
},
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 50), // κ°λ‘ 100% λ²νΌ
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero, // ν
λ리λ₯Ό λ₯κΈμ§ μκ² μ€μ
),
),
child: const Text('μμ νκΈ°'),
),
),
),
);
}
/// μμ νμΈ λ€μ΄μΌλ‘κ·Έ νμ λ©μλ
Future<bool> _showDeleteConfirmDialog() async {
bool result = false;
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('μμ νμΈ'),
content: Text('μ λ§λ‘ μ΄ κ²μκΈμ μμ νμκ² μ΅λκΉ?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false); // μ·¨μλ₯Ό ν΄λ¦νλ©΄ false λ°ν
},
child: Text('μ·¨μ'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true); // μμ λ₯Ό ν΄λ¦νλ©΄ true λ°ν
},
child: Text('μμ '),
),
],
);
},
).then((value) {
result = value ?? false;
});
return result;
}
}
Dart
볡μ¬