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 이라는 환경 변수가 있다는 의미.
./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


