inblog logo
|
[HootJem] 개발 기록 블로그
    flutter

    서버 통신하기-1. 설정

    서버 설정과 데이터 조회 하는법
    HootJem's avatar
    HootJem
    Oct 14, 2024
    서버 통신하기-1. 설정
    Contents
    1. 화면 내려받기2. 서버 내려 받고 실행3. 완성 플러터 코드4. 돌아가는 방향5. 배포Post

    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. 돌아가는 방향

    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
     

    💡
    1. 서버 통신 - jar 파일 만들기 / 설정 https://inblog.ai/hj/서버-통신하기1-설정-31514
    1. 서버 통신 - 상세 페이지 보기 https://inblog.ai/hj/서버-통신하기2-상세-페이지-보기-31515
    1. 서버 통신 - 글쓰기 https://inblog.ai/hj/서버-통신하기3-글쓰기-31518
    1. 서버 통신 - 게시글 삭제 https://inblog.ai/hj/서버-통신하기4-게시글-삭제-31519
    Share article
    Contents
    1. 화면 내려받기2. 서버 내려 받고 실행3. 완성 플러터 코드4. 돌아가는 방향5. 배포Post

    [HootJem] 개발 기록 블로그

    RSS·Powered by Inblog