1. 화면 내려받기
flutter-blog-curd-basic-start
samsung-lec • Updated Aug 1, 2024
2. 서버 내려 받고 실행
spring-blog-crud-basic
samsung-lec • Updated Aug 1, 2024
java -jar 파일명
3. 완성 플러터 코드
flutter-blog-curd-basic-end
samsung-lec • Updated Aug 2, 2024
4. 돌아가는 방향

Jar 파일을 테스트 서버에 던져서 문제없이 잘 되면 운영 서버로 릴리즈 한다.
에러가 생긴 경우에는
핫픽스
브랜치를 만들어 수정을 한다.파스(?) 를 사용하여 직접 플랫폼을 만들어 보았나…?
doker 로 가상환경을 만들어 본인의 윈도우 상에서 테스트 환경을 만들어 볼 수 있다.
배포가 완료되 운영 서버에서 돌아가는 서버를 모니터링 해야한다.
모니터링은 바로 남겨진 로그와 알림을 사용하여 할 수 있다 😎
문제가 생겼을 때 바로 올리는게 아니라 테스트 하고 문제 없을 시 운영서버에 올리는 것.
플루터를 Jar 에 연결할건데, 이때 Ipconfig 로 나온 그 해당 ip로 요청을 보내야함.
localhost
로 되지 않음. (에뮬레이터일때)
크롬으로 실행하면 컴파일링이 JS(jaca Script) 로 바뀐다. 이러면 Jar 을 거부함. 이게 바로 CORS (자바 스크립트로 오는 요청을 도메인이 다를 때 막는다) 스프링 서버에서 CORS 설정을 해줘야 먹음.
모든 요청을 허용할 수는 없기 때문에 프론트로 돌고있는 ip 만 허용 시켜주면 된다. (필터로 걸어 놓으면 됨. 자바 스크립트 요청인데, 내가 설정한 ip 면 통과 처리)5. 배포


HOME 이라는 환경 변수가 있다는 의미.
- 임의로 환경변수 만들어 봄

export 로 만든 환경 변수의 경우에는 컴퓨터가 껏다 켜질 때 날아가게 된다.
./gradlew clean build



$ java -jar spring-blog-river-0.0.1-SNAPSHOT.jar
실행 시키는겨
일단 윈도우에서 파이어월로 엥간한 포트 요청 다 막힌거.
이제 어떤 포트를 열지 정하는(?) 실행했을 때 8080이 시작 되었음을 알 수 있음

열린 포트는 누구나 들어올 수 있따.
아웃바운드는 엥간하면 다 열려있다. 인바운드가 규칙이 있을뿐(기본적으로 닫아놓고 있다.)
이제 어디 요청을 보내야하나? 바로
api 문서
를 참조해서 해야함.주소 | ㅤ | ㅤ |
/api/post/1 | {
"status": 200,
"msg": "성공",
"body": {
"id": 1,
"title": "title 1",
"content": "content 1",
"createdAt": "2024-10-10 10:27:54",
"updatedAt": "2024-10-10 10:27:54"
}
} | ㅤ |

크로스 머시기 설정 하자.
core 에 파일 추가해서 ip 추가해줌.


코드를 살펴보다.
class PostListBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 1. ViewModel 이 만들어 져야함.
// 2. 만들어 지면 Watch 로 보고 있어야 함(통신 완료 후 데이터 뿌리기)
// 3. ViewModel 을 만든다. (위치, 페이지 있는데)
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
leading: Text("1"),
title: Text("제목입니다"),
trailing: IconButton(
icon: Icon(Icons.arrow_forward_ios),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostDetailPage(),
));
},
),
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: 20),
);
}
}

데이터를 select 해서 뿌릴 거면 view model 이 필요하다. 그냥 write 할거면 필요 x
vm 은 크게 이렇게 구성된다.
// 1. 창고 (ViewModel)// 2. 창고 데이터 (State)// 3. 창고 관리자 (Provider)
이때 창고 데이터를 따로 만드는 이유는 class 이기 때문이다. 만약 단순히 int 값만 관리할 거면 창고 데이터를 따로 만들 필요가 없다.
그냥 provider 는 전역적으로 사용하고 상태 변경이 없는 프로바이더이다.
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 창고 (ViewModel)
class PostListVM extends StateNotifier<PostListModel> {
PostListVM(super.state);
}
// 2. 창고 데이터 (State)
class PostListModel {}
// 3. 창고 관리자 (Provider)
final postListProvider =
StateNotifierProvider<PostListVM, PostListModel>((ref) {});
스니펫으로 만드시면 더 편해염

물음표 추가
완성 코드
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 창고 (ViewModel)
class PostListVM extends StateNotifier<PostListModel?> {
PostListVM(super.state);
}
// 2. 창고 데이터 (State)
class PostListModel {}
// 3. 창고 관리자 (Provider)
final postListProvider =
StateNotifierProvider<PostListVM, PostListModel?>((ref) {
return PostListVM(null);
});
Post
import 'package:blog/ui/list/post_list_vm.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 창고 (ViewModel)
class PostDetailVm extends StateNotifier<PostDetailModel?> {
PostDetailVm(super.state);
// 자기 상태만 바꾸기 때문에 절대 리턴 값을 주지 않는다.
Future<void> notifyInit() async {
// 1. 통신을 통해 응답 받기
// 2. 상태 갱신
}
}
// 2. 창고 데이터 (State)
class PostDetailModel {
List<_Post> posts;
PostListModel(this.posts);
}
class _Post {
int id;
String title;
_Post.fromMap()
}
// 3. 창고 관리자 (Provider)
final postListProvider =
StateNotifierProvider<PostDetailVm, PostDetailModel?>((ref) {
return PostDetailVm(null);
});
깊은 복사를 해서 print 되게 할 수 있다.
String json = '{"id":1, "title":"제목1"}';
Map<String,dynamic> map = {
"id":1,
"title":"제목1"
};
class Post {
int id;
String title;
Post(this.id, this.title);
}
void main() {
Post post = Post(map["id"], map["title"]);
print(post.id);
print(post.title);
}
이렇게 하나하나 받는 것 보다 Map 을 받아서 컨버팅하는 생성자를 만드는 것이 더 편할 수 있음.
String json = '{"id":1, "title":"제목1"}';
Map<String,dynamic> map = {
"id":1,
"title":"제목1"
};
class Post {
int id;
String title;
Post.fromMap(map) :
this.id = map["id"],
this.title = map["title"];
}
void main() {
Post post = Post.fromMap(map);
print(post.id);
print(post.title);
}
꺼내서 넣기 보다 그냥 map 을 넣 으면 편하자나
class PostRepository {
void findAll() {
dio.get("serverIP/api/post");
}
}
바로 꺼내고 싶다면? 이렇게 해놓으면 굿
import 'package:dio/dio.dart';
final serverIP = "http://192.168.0.65:8080";
final Dio dio = Dio(
BaseOptions(baseUrl: serverIP),
);
에러 났던 이유 http 랑 utils 랑 코드 겹쳐서, httl 지우고
바디데이터를 아까 걔한테 전달 해 줄거임
import 'package:blog/core/utils.dart';
import 'package:dio/dio.dart';
class PostRepository {
void findAll() async {
// 1. 통신 -> response [deader, body]
Response response = await dio.get("/api/post");
// 2. body 부분 리턴
var responseBody = response.data;
// list 의 map 타입
return responseBody["body"];
}
}
import 'package:blog/core/utils.dart';
class PostRepository {
Future<List<dynamic>> findAll() async {
// 1. 통신 -> response [deader, body]
var response = await dio.get("/api/post");
// 2. body 부분 리턴
List<dynamic> responseBody = response.data;
// list 의 map 타입
return responseBody;
}
}
언제는 object 고 언제는 list 의 map 타입임 그래서 똑바로 데이터 타입 통일 시키기.
body 부분이 json array 면 List<dynamic> 으로 받기
body 부분이 json 이면 Map<String, dynamic> 으로 받기
얘는
json array
라서 List<dynamic>
가 됨.import 'package:blog/data/post_repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 창고 (ViewModel)
class PostListVM extends StateNotifier<PostListModel?> {
PostListVM(super.state);
Future<void> notifyInit() async {
// 1. 통신을 통해 응답 받기
List<dynamic> list = await PostRepository().findAll();
// 2. 파싱
List<_Post> posts = list.map((e) => _Post.fromMap(e)).toList();
// 2. 상태 갱신
}
}
// 2. 창고 데이터 (State)
class PostListModel {
List<_Post> posts;
PostListModel(this.posts);
}
class _Post {
int id;
String title;
_Post.fromMap(map)
: this.id = map["id"],
this.title = map["title"];
}
// 3. 창고 관리자 (Provider)
final postListProvider =
StateNotifierProvider<PostListVM, PostListModel?>((ref) {
return PostListVM(null);
});
상태관리 할 클래스로 간다.



import 'package:blog/ui/detail/post_detail_page.dart';
import 'package:blog/ui/list/post_list_vm.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PostListBody extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. ViewModel이 만들어져야함 watch로 보기
PostListModel? model = ref.watch(postListProvider);
if (model == null) {
return CircularProgressIndicator();
} else {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
leading: Text("${model.posts[index].id}"),
title: Text("${model.posts[index].title}"),
trailing: IconButton(
icon: Icon(Icons.arrow_forward_ios),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostDetailPage(),
));
},
),
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: model.posts.length),
);
}
}
}
에뮬레이터 실행하니 이런 에러 발생.



이렇게 감싸 줘야함.

- 서버 통신 - jar 파일 만들기 / 설정 https://inblog.ai/hj/서버-통신하기1-설정-31514
- 서버 통신 - 상세 페이지 보기 https://inblog.ai/hj/서버-통신하기2-상세-페이지-보기-31515
- 서버 통신 - 글쓰기 https://inblog.ai/hj/서버-통신하기3-글쓰기-31518
- 서버 통신 - 게시글 삭제 https://inblog.ai/hj/서버-통신하기4-게시글-삭제-31519
Share article