- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요. 좋은 강의 정말 잘 듣고 있습니다. 강의를 통해 MVVM 구조와 클린 아키텍처에 대해 배우고, 진행 중인 프로젝트에 적용해보고 있는데, 어려움을 겪고 있어 조언을 구하고자 질문 글을 올리게 되었습니다. 회원 정보 관련 api - repository - use_case - view_model -view 구조에서, 로그인 화면에서 로그인 후 받아오는 회원 정보를 어디에 저장해 두어야 되는지 잘 모르겠습니다. 회원정보는 마이페이지, 피드 등 여러 view에 사용되는데, 그렇다면 각각의 view_model은 회원정보를 어디에서 읽어오는게 좋은지 궁금합니다. 새로 고침 관련 note_app 강의 예제 에서는 노트 목록과 삭제가 하나의 화면(notes_view)에 있습니다. 제가 구현하는 구조에서는 두 개의 view로 분리되어 있습니다. 1번 view: 노트 목록 화면 2번 view: 노트 상세 + 삭제 화면 각 화면은 서로 다른 view_model을 사용합니다. 노트 상세 화면에서 노트를 삭제했을 때, 노트 목록 화면을 어떻게 다시 업데이트 하는 것이 적절한지 고민입니다. 생각나는 방법을 아래에 작성해보았는데, 어떤 방법을 사용하는 게 깔끔할지, 이외에 더 좋은 방법이 있는지 궁금합니다. 두 개의 view가 하나의 view_model을 공용으로 사용 노트 상세 화면에서 노트 목록 view_model를 read 하여 _loadNotes()를 직접 호출 노트 목록도 회원 정보처럼 별도로 저장해두고, view_model은 읽어오기 감사합니다!
디바이스 선택시 Chrome(web)과 저의 실물 핸드폰 연결해서 했더니 안드로이드 스튜디오 코드는 잘 돌아갑니다. 하지만 BoardViewModel.dart 코드에서 설정한 url그대로 따라 쳤는데 swagger ui에서 계속 화면처럼 response body가 비어있습니다.. 계속나와서 혹시 전체 소스코드 공유 안될까요??
안녕하세요...강의 잘 보고있습니다. 일단 로그인을 합니다.. 그런데 이때 각 소속회사별로 로그인을 합니다. (A회사로 로그인을 하면... 다음부터는 A회사의 정보만 조회를 해야 합니다.) 그런 경우 공지사항 계시판이 메인화면에 있는데.이때 공지 화면의 내용은 당연히 A회사의 공지사항만 나와야 하는데.. 서버 컴포넌트에 파라미터를 넘겨야 하는데. 어떻게 넘겨야 할까요? zustand같은 전역상태도 사용 못하고.. 쿠키를 이용해야하나요? 현재는 http:url/index?blngcocd=999 이런식으로 소속회사를 달고 다나는데... 이건 아닌거 같아서요?
킬구형 5장. 작전2: Run Squad 장악 작전 공부중인데, Process finished with exit code 0 이라는 문구 자체가 나오질 않아. 혹시나 싶어서 새 프로젝트 만들어서 시도해봐도 마찬가지고,, 윈도우 11, java 17에 org.springframework.boot:spring-boot-starter-batch:3.5.7 환경에서 개발중인데 혹시 버전이나 OS 문제일까?? 아니면 뭔가 설정을 빼먹은건지 모르겠네.. 혹시 몰라서 application.yaml 설정도 첨부해 spring: application.name: demo output.ansi.enabled: always datasource: url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 driver-class-name: org.h2.Driver username: sa password:
제로초님 안녕하세요 expo 프로젝트의 폴더 구조에 대해서 고민이 있습니다.!! 현재 src/app 폴더에서 페이지들을 관리하고 있고 각 페이지 내부에서만 사용하는 하위 컴포넌트들은 src/app/settings/_components/AccountSettingsSection.tsx 와 같이 _components 폴더 하위에 배치하고, named export를 통해 라우트로 잡히지 않게 하고 있습니다. 문제는 없지만 프로젝트를 실행시키면 WARN Route "./settings/_components/AccountSettingsSection/AccountSettingsSection.tsx" is missing the required default export. Ensure a React component is exported as default. 라는 경고 메시지가 app/ 내부에서 named export 한 모든 하위 컴포넌트를 대상으로 출력됩니다. Next.js처럼 _components 내부의 하위 컴포넌트들은 완전히 무시하도록 하고 싶은데 아무리 찾아봐도 무시할 방법이 없는 거 같습니다 ㅠ 제로초님께서는 어떤 방법으로 페이지 내부 하위 컴포넌트들을 배치하시는지 궁금합니다..!! 관리하기 용이한 폴더 구조가 있다면 추천해주실 수 있으신가요??
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 여기에 질문 내용을 남겨주세요. 이 부분에서 Student student를 선언하고 new Student();를 안했는데 어떻게 바로 .name같은 거를 사용할 수 있는 건가요? 다른 부분에서는 꼭 객체를 먼저 생성해야 접근을 할 수 있는데 이 함수에서는 왜 가능한건가요? static void initStudent(Student student, String name, int age, int grade) { student.name = name; student.age = age; student.grade = grade; }
[코드팩토리] [초급] Flutter 3.0 앱 개발 - 10개의 프로젝트로 오늘 초보 탈출!
강의 따라서 프로젝트를 진행하다가 '출근 가능/불가능 확인 로직'에서 문제를 발견했습니다. 이것저것 시도하다가 드디어 해결해서 과정 공유해봅니다. 기존 코드 /// 기존의 initState() @override void initState() { super.initState(); // (아마도) 이 시점에서 오류 발생 ↓ Geolocator.getPositionStream().listen((eventPosition) { // 목적지 위치 var start = defaultLatLng; // 현재 위치 var end = LatLng(eventPosition.latitude, eventPosition.longitude); /// 거리 구하기 var distance = Geolocator.distanceBetween( start.latitude, start.longitude, end.latitude, end.longitude, ); /// 거리가 `boundary` 내부에 들어오는지 확인 setState(() => mayChoolCheck = distance <= boundary); }); } /// 위젯 빌드 @override Widget build(BuildContext context) { /// AppBar: 제목 & 액션 버튼1 var appBar = AppBar( title: Text( '오늘도 출근', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.w700), ), centerTitle: true, // 안드로이드 설정 actions: <Widget>[ /// 원래 위치로 돌아오기 버튼 IconButton( onPressed: returnToMyLocation, icon: Icon(Icons.my_location), color: Colors.blue, ), ], ); return Scaffold( backgroundColor: Colors.white, appBar: appBar, /// 권한 요청을 위한 빌더 body: FutureBuilder<void>( future: checkPermission(), // 위치 권한 묻기 대화상자는 이 시점에 생성됨 builder: (context, snapshot) { if (snapshot.hasError) { return Center(child: Text('${snapshot.error}')); } // 이하 위젯 생성 코드... }, ), ); } /// Future<void> checkPermission() async { /// 위치 서비스 활성화 여부 var isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled(); /// 활성화되지 않았다면 에러 발생 (앱이 현재 위치를 사용할 수 없으므로) assert(isLocationServiceEnabled, '위치 기능을 활성화하지 않았습니다.'); /// 권한 상태 확인 var checkedPermission = await Geolocator.checkPermission(); if (checkedPermission == LocationPermission.denied) { checkedPermission = await Geolocator.requestPermission(); } /// 시뮬레이션 상 `checkedPermission`이 위치 권한 허용 상태가 아니면 에러 유도 assert( checkedPermission == LocationPermission.always || checkedPermission == LocationPermission.whileInUse, '위치 권한을 허용해주세요.', ); } 오류 추적 오류의 원인은 'User denied permissions .. device's location.' 즉, 사용자가 위치 권한을 아직 허용하지 않은 시점에 위치 권한이 필요한 함수를 호출하려고 함. [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: User denied permissions to access the device's location. E/flutter (13339): #0 GeolocatorAndroid.getPositionStream.<anonymous closure> (package:geolocator_android/src/geolocator_android.dart:201:9) E/flutter (13339): #1 Stream.handleError.<anonymous closure> (dart:async/stream.dart:961:16) E/flutter (13339): #2 HandleErrorStream.handleError (dart:async/stream_pipe.dart:303:17) ... #0 에서 이미 getPositionStream().<anonymous closure> 로 추적한 걸로 보아, Geolocator.getPositionStream() 함수 호출 시점에 오류 발생한 것이 분명함. 문제의 원인과 해결책 (일부 ChatGPT의 도움을 받음) 문제의 가장 중요한 핵심은, 처음 앱 실행 시 위치 권한 확인 로직이 완전히 끝나기 전에 위치 권한 허용 이 이루어지지 않으므로, 앱 재시작 이전까지 '사용자 현재 위치 추적 기능'이 활성화되지 않고, 추가로 관련 함수에서 오류 발생 가능성이 있음. 그러므로 checkPermission() 함수는 initState() 에서 최초 한 번만 실행하고, Geolocator.getpositionStream() 함수는 따로 분리하여 checkPermission() 이 정확히 끝난 시점에 실행되도록 로직을 변경함. 1. checkPermission() 의 리턴 타입을 Future<void> 에서 Future<bool> 로 변환 /// 위치 권한 확인 및 부여 함수 Future<bool> checkPermission() async { /// 위치 서비스 활성화 여부 var isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled(); assert(isLocationServiceEnabled, '위치 기능을 활성화하지 않았습니다.'); /// 권한 상태 확인 var checkedPermission = await Geolocator.checkPermission(); if (checkedPermission == LocationPermission.denied) { checkedPermission = await Geolocator.requestPermission(); } /// 위치 권한 허용 여부를 리턴 return checkedPermission == LocationPermission.always || checkedPermission == LocationPermission.whileInUse; } 2. initState() 에서 checkPermission() 과 addSubscription() 함수를 차례로 실행 ( .then() 메소드 활용) @override void initState() { super.initState(); checkPermission().then((permitted) { if (permitted) addSubscription(); }); } /// addSubscriptsion 함수 void addSubscription() { /// 리스너 함수: 출근체크 가능 여부 확인하기 Geolocator.getPositionStream().listen((eventPosition) { // 목적지 위치 var start = defaultLatLng; // 현재 위치 var end = LatLng(eventPosition.latitude, eventPosition.longitude); /// 거리 구하기 var distance = Geolocator.distanceBetween( start.latitude, start.longitude, end.latitude, end.longitude, ); /// 거리가 `boundary` 내부에 들어오는지 확인 setState(() => mayChoolCheck = distance <= boundary); }); } 3. 위젯 재빌드 시 FutureBuilder 의 future 함수 재호출을 방지하기 위해 비동기 결과를 미리 필드로 빼놓기 ChatGPT는 이를 'Cashing Future' 라고 부른다고 함. (이 코드 부분에 중요한 로직이 하나 빠져서 방금 재수정했습니다.) /// 권한 확인 여부 리턴 값 late final Future<bool> permissionFuture; @override void initState() { super.initState(); /// checkPermission()의 결과를 값으로 저장 permissionFuture = checkPermission().then((permitted) { if (permitted) addSubscription(); return permitted; }); } /// 위젯 빌드 @override Widget build(BuildContext context) { /// AppBar: 제목 & 액션 버튼1 var appBar = AppBar(...); return Scaffold( backgroundColor: Colors.white, appBar: appBar, /// 권한 요청을 위한 빌더 body: FutureBuilder<bool>( future: permissionFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) { return ColoredBox( color: Colors.blue, child: Center(child: CircularProgressIndicator()), ); } /// snapshot.data가 false 값인 경우는 사용자가 위치 권한 요청을 거부했을 때 if (snapshot.hasError || snapshot.data! == false) { return Center(child: Text('위치 권한을 허용해주세요.\n${snapshot.error}')); } /// 이하 위젯 생성 코드 ... }, ), ); } 이렇게 했을 때 앱 삭제 후 다시 빌드 했을 때, 그리고 재시작 했을 때도 정상적으로 잘 작동했습니다. 코드가 많이 훼손된 느낌도 들긴 하지만.. 나름대로 공부가 돼서 기분은 좋네요. 주저리주저리 쓰느라 정신 없었는데.. 잘 이해가 안 되거나 하고 싶은 피드백이 있다면 꼭 말씀해주세요! 아무쪼록 끝까지 읽어주셔서 감사합니다.