서버 통신하기-1. 설정

서버 설정과 데이터 조회 하는법
HootJem's avatar
Oct 14, 2024
서버 통신하기-1. 설정

1. 화면 내려받기

flutter-blog-curd-basic-start
samsung-lecUpdated Aug 1, 2024

2. 서버 내려 받고 실행

spring-blog-crud-basic
samsung-lecUpdated Aug 1, 2024
java -jar 파일명

3. 완성 플러터 코드

flutter-blog-curd-basic-end
samsung-lecUpdated Aug 2, 2024
 

4. 돌아가는 방향

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

5. 배포

notion image
notion image
HOME 이라는 환경 변수가 있다는 의미.
  • 임의로 환경변수 만들어 봄
    • notion image
      export 로 만든 환경 변수의 경우에는 컴퓨터가 껏다 켜질 때 날아가게 된다.
       
./gradlew clean build
notion image
notion image
notion image
$ java -jar spring-blog-river-0.0.1-SNAPSHOT.jar
실행 시키는겨
 
일단 윈도우에서 파이어월로 엥간한 포트 요청 다 막힌거. 이제 어떤 포트를 열지 정하는(?) 실행했을 때 8080이 시작 되었음을 알 수 있음
notion image
열린 포트는 누구나 들어올 수 있따.
아웃바운드는 엥간하면 다 열려있다. 인바운드가 규칙이 있을뿐(기본적으로 닫아놓고 있다.)
 
이제 어디 요청을 보내야하나? 바로 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" } }
notion image
크로스 머시기 설정 하자.
 
core 에 파일 추가해서 ip 추가해줌.
notion image
일단 삭제 시킨다
일단 삭제 시킨다
코드를 살펴보다.
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), ); } }
notion image
데이터를 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) {});
스니펫으로 만드시면 더 편해염
 
notion image
물음표 추가
완성 코드
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); });
 
 
상태관리 할 클래스로 간다.
notion image
notion image
notion image
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), ); } } }
에뮬레이터 실행하니 이런 에러 발생.
notion image
notion image
notion image
이렇게 감싸 줘야함.
notion image
 

Share article

[HootJem] 개발 기록 블로그