묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
인터셉터 질문입니다
1)인터센터 구현하실 때 storage를 주입해서 사용했고final FlutterSecureStorage storage;다른 스플래쉬 스크린에서는 const로 common > const > data.dartfinal storage = FlutterSecureStorage(); 에 있는 storage를 가져다 쓰고 있습니다.stateless, stateful widget도 결국 class가 청사진 이라고 생각되는데모두 storage;를 주입 받아야 하지 않나요? 궁금합니다..! 2)인터셉터에서 선생님께서 onRequest 에 return super.onRequest(options, handler); 부분이 핸들러가 에러를 발생시킬지 , 정상처럼 진행시킬지 알아서 결정한다고 하셔서 그럼 혹시 여기서도 return handler.reject와 return handler.resolve를 쓸 수 있나요?? 3) 인터셉터에서 onError에 if(/auth/token 경로도 아니고 && 401일때){ ...}그외에는 return handler.reject(err)를 사용했는데 사실 그대로 에러를 내는 거면 return super.onError(err,handler); 를 써도 되지 않나요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
PaginationProvider의 로직 중 이해안가는 부분이 있습니다.
RestaurantPagination-5 2:25경에 나오는 부분입니다.if (fetchMore) { final pState = state as CursorPagination<T>; state = CursorPaginationFetchingMore( meta: pState.meta, data: pState.data, ); paginationParams = paginationParams.copyWith( after: pState.data.last.id, ); } // 데이터를 처음부터 가져오는 상황. else { // 만약에 데이터가 있는 상황이라면 // 기존 데이터를 보존한 채로 Fetch를 진행한다. // 여기!! if (state is CursorPagination && !forceRefetch) { final pState = state as CursorPagination<T>; state = CursorPaginationRefetching<T>( meta: pState.meta, data: pState.data, ); } else { state = CursorPaginationLoading(); } }해당로직에서 "// 여기!!"로 표시한 부분에서if(state is CursorPagination && !forceRefetch)조건문이 이해가지 않습니다.fetchMore는 아닌데 새로고침(forceRefetch)도 아니다. 그런데 데이터를 이미 가지고 있고 새로운 데이터를 요청한다?이게 어떤 경우를 의미하고 작성된 케이스일까요?
-
미해결Only Javascript만으로 배우는 하이브리드앱 패키징
onesignal 연동 시 black screen
window.plugins.OneSignal.setAppId(); 위 코드 입력 시 화면이 black screen으로 표시됩니다. 하지만 onesignal 연동도 잘되고 푸시 메시지 발송되 잘 됩니다. 원인이 무엇일까요? android 구버전과 최신버전 모두에서 그렇습니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
retrofit multipartfile 리스트 보내기
@POST('/boards') @Headers({'Authorization': 'true'}) @MultiPart() Future<HttpResponse> createBoard({ @Part() required BoardRequestModel boardCreateModel, @Part() List<MultipartFile>? files, });이렇게 multipartfile 리스트로 이미지를 보내려고 하는데생성된 g.dart 파일을 보면 이렇게 에러가 발생합니다현재 getx 에서 Multipartfile을 hide 해주고 사용했습니다혹시 retrofit을 이용할때 multipartfile 리스트를 보내는 방법이 따로 있을까요?
-
해결됨[입문] Qt QML과 C++로 시작하는 크로스플랫폼 앱 개발
visual studio 2019 버전 다운로드가 안보여요.
강의 "개발환경 셋팅" 보고 있는데요.MSVC 2019 설치 링크에 들어가 봤는데, 저는 visual studio 2019 다운로드 버튼이 안보이네요. 다운로드 받을 수 있는 방법이 있을까요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
Rating의 get 요청이 가질 않습니다.
안녕하세요. 강의를 들으며 ratingState를 print 해보니 Instance of '_RestaurantRatingRepository' 결과가 나왔습니다. 이제보니 dio 요청 자체도 가지 않은 상태입니다. pagination_provider.dart를 적용한 후 Restaurant와 RestaurantDetail은 잘 실행되는데, Rating만 dio 요청 자체가 가지 않습니다.저 아래의 Instance of '_RestaurantRatingRepository'가 ratingState입니다.디버깅을 통해서 dio를 제대로 받아온 것은 확인했는데, pagination 내부의 print는 restaurant만 먹히고, restaurantdetail과 rating은 print되지 않아 어떻게 해결해야할지, 어떻게 해결 방법을 찾아야 할지 갈피도 못 잡겠습니다.프로젝트의 repository를 첨부합니다. https://github.com/jungeyo3/codefactory-flutter-lv2-study 버전은 아래와 같습니다. 감사합니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
redirect와 refresh 질문드려요
안녕하세요 선생님 ^^강의 듣고 있는중에 궁금한 것이 생겼습니다 1)gorouter 7버전에서듣고(4버전 건너뛰고) -> 프로젝트에 적용하기 단원보고 있는데요 갑자기 refresh라는 개념이 나와서요 7버전에서는 없었는데 .... refresh라는 기능 없이도 충분히 만들수 있나요?아니면 7버전에서 refresh비슷한 기능이 있나요?? 2)redirect를 통해 이동한 페이지는 Appbar에서 뒤로가기가 없던데 이것이 redirect 특징인 건가요? 3)redirect를 사용해서 뒤로가기버튼이 없는 상태에, 만약에 context.pop을 하게 된다면 어디로 이동하게 되나요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
에러가 생겨서 질문드립니다
에러 내용W/WindowOnBackDispatcher(25534): OnBackInvokedCallback is not enabled for the application.W/WindowOnBackDispatcher(25534): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest. 증상:실행시키면 녹색바탕에 codefactory 로고 와 인디케이터 화면이 나오면서 무지 오래 20분 이상 계속 되다가 로그인 화면으로 들어가네요로그인 화면으로 바로 들어가야 하는데요. 그게 안되고 있습니다.로그인 화면으로 들어갈 때 첨부한 사진 하단 2줄 메세지가 나오네요 (에러 내용 참조) 검색하면 증상 대처법이 나오는데 따라해도 똑같습니다.설정 어디를 바꾸면 될거도 같은데 찾지 못하고 있습니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
BottomNavigationBar 질문
검색 방법이 잘못된건지 모르지만 구글에 검색해도 해결법이 나오지않아 질문 올립니다. bottomNavagtionBar: GestureDetector( onTap: (){ Navigator.push(context, MaterialPageRoute(builder: (context) => CommentRegisterScreen( id: widget.id))); }, child: TRoundedContainer( height: 60, backgroundColor: const Color(0xffF8F8FA), padding: const EdgeInsets.all(TSizes.defalutSpace), radius: 0, child: Text( '댓글을 남겨보세요.', style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.grey), ), ), ), child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(TSizes.defalutSpace), child: Column( children: [ const SizedBox(height: TSizes.spaceBtwItems), SizedBox( height: MediaQuery.of(context).size.height, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: PaginationListViewV2( model: state, id: widget.id, provider: commentProvider(widget.id), itemBuilder: <Comment>(_,index,comment){ if(index == 0){ return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// 제목 Text(state.title, style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: TSizes.spaceBtwItems), /// 닉네임 Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ const TRoundedImage( width: 24, height: 24, fit: BoxFit.fill, imageUrl: 'asset/img/no_image.png', borderRadius: 100, ), const SizedBox(width: TSizes.sm), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( state.creator, style: Theme.of(context).textTheme.bodyLarge, ), ], ), ], ), const SizedBox(height: TSizes.spaceBtwItems), /// 날짜, 좋아요 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( state.createDate.split("T")[0], style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.grey), ), GestureDetector( onTap: button.disable==false ? () async { //버튼 비활성화 await button.change(); await check == -1 ? ref.read(communityProvider.notifier).clickFavorite(widget.id) : ref.read(communityProvider.notifier).downFavorite(widget.id); await ref.read(favoriteProvider.notifier).updateFavorites(widget.id); //버튼 활성화 await button.change(); } : null, child: check == -1 ? TRoundedContainer( showBorder: true, padding: const EdgeInsets.all(TSizes.sm), borderColor: Colors.redAccent, radius: 10, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '좋아요', style: Theme.of(context).textTheme.labelMedium!.copyWith(color: Colors.redAccent), ), const SizedBox(width: TSizes.xs), Text( state.favorite.toString(), style: Theme.of(context).textTheme.labelMedium!.copyWith(color: Colors.redAccent), ), ], ), ) : TRoundedContainer( padding: const EdgeInsets.all(TSizes.sm), backgroundColor: Colors.redAccent, radius: 10, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ child: SizedBox( height: 120, child: ListView.separated( shrinkWrap: true, scrollDirection: Axis.horizontal, itemCount: imgs.length, separatorBuilder: (_, __) { return const SizedBox(width: TSizes.spaceBtwItems / 2); }, itemBuilder: (_, int index) { return TRoundedImage( imageUrl: imgs[index].toString().trim(), width: 120, height: 120, fit: BoxFit.fill, borderRadius: 12.0, isNetworkImage: true, ); }, ), ), ), ], ) : const SizedBox(), const SizedBox(height: TSizes.spaceBtwSections), /// 글 내용 Text( state.content.trim(), style: Theme.of(context).textTheme.bodyMedium!.copyWith(height: 1.5), ), /// 댓글 0이 아닐 경우 Row( children: [ Text('댓글', style: Theme.of(context).textTheme.bodySmall), const SizedBox(width: TSizes.spaceBtwItems / 2), Text(state.commentCnt.toString(), style: Theme.of(context).textTheme.bodySmall), ], ), const SizedBox(height: TSizes.spaceBtwItems), CommentCard( comment:comment, recomments:comment.commentList, board_id: widget.id ) ], ); } return CommentCard( comment:comment, recomments:comment.commentList, board_id: widget.id ); }), ), Container( height: 210 ) ] ), ), // Center(child: Text('첫 댓글을 남겨주세요.', style: Theme.of(context).textTheme.bodySmall)), ], ), ), ),위 코드와 같이 BottomNavigationBar와 SinglechildScrollView를 같이 사용하고 있습니다. 가장 하단의Container( height: 210 )위 코드를 넣어주지 않을 시 화면 스크롤이 모두 안되고 짤리는 현상이 발생합니다. 혹시 이러한 경험이 있어 해결방법이 있다면 알려주시면 감사하겠습니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
다시 getDetail호출?
제 생각이 조금이라도 이상한 곳이 있다면 말씀해주세요 ~! ㅎㅎ첫번째 질문은 , detail_screen.dart에서initState에 ref.read(restaurantProvider.notifier).getDetail(id: widget.id); 만 사용하였는데만약 restaurantDetailModel이 이미 있는 id를 또 getDetail한 거였다면, 다시 getDatil을 호출할 필요가 없으므로이떄 initState에 ref.read(restaurantDetailProvider) is restaurantDetailModel 라면 getDatil을 호출하지 않겠다를추가로 넣으면 좋겠다 생각하는데 제 생각이 어떠한가요?? 두번째 질문은 restaurant > provider > restaurant_provider.dart 에서 네트워크 요청을 하다가 등등 에러가 날수 있을 거 같은데 여기는 catch 로 잡아서 state를 왜 error로 만들지 않았나 궁금합니다..!
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
데이터가 List형태일때 질문해 봅니다.
안녕하세요. 제가 질문을 잘못했던거 같습니다.데이터가 Map형태가 아니고 List형태일때 CursorPagination을 사용할 수 있나요?목적은 CursorPaginationBase를 extends 하고 싶어서 입니다. 된다면 CursorPagination을 어떻게 구성해야 하는지 힌트를 좀 주실 수 있을까요?아니면 보통 데이터를 Map 형태로 고쳐서 사용하나요?아니면 제가 잘못이해한걸까요?데이터가 meta나 data안에 있어야 하는게 Map형태라고 생각했는데 그게 아니고List형태도 key와 value로 나누면 Map형태가 되는건가요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
스크롤의 maxScrollExtent 관련 문의
안녕하세요 저도 완성된 pagination 로직 실행해보기 강의에서 스크롤 리스너의 if문 조건이 적용되지 않아서 문의 드립니다. 저는 구현된 코드를 확인했을때 maxScrollExtent 값이 엄청 크게 나옵니다.그래서 같은 리스트를 세번 정도는 반복 스크롤을 해야 컨트롤러의 offset이 maxScrollExtent와 동일해집니다. 최초 리스너 동작 시 scroll 위치 : 6.6650390625maxScrollExtent : 7485.526283048211 확인해본 사항은 아래와 같습니다. item 중복 저장 여부 : 최초 로딩 후 데이터 사이즈를 확인했을때도 사이즈는 20개 입니다. 리스트뷰 구현 오류인지 확인 : 컨트롤러를 빼고 테스트하면, 20개 크기 그대로 리스트뷰가 생성됩니다. 사이즈가 안먹히는 것 같아서 픽셀 위치 대신 데이터 갯수를 index로 관리해서 해보려고 했는데, 정확히 맞는 방법인지도 모르겠어서 문의드립니다. T_T...어딘가 단단히 잘못된 것 같은데 모르겠습니다...
-
해결됨[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
dio요청시 에러 상태코드임에도 onError가 아닌 onReponse에서 응답
flutter: [REQ] [METHOD] GET [ENDPOINT] http://localhost:8080/api/user/info flutter: [RES] [METHOD] GET [ENDPOINT] http://localhost:8080/api/user/info [STATUS CODE] 401현재 제가 진행 중인 프로젝트에서 401 코드임에도 불구하고 onError가 아닌 onReponse로 넘어갑니다. 액세스 토큰 재발급 과정을 onResponse에서 진행해도 되는걸까요? throw new InvalidVerificationCodeException(ErrorCode.INVALID_CODE); }백엔드에서 이렇게 에러를 내주는 onResponse로 넘어오네요..
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
checkToken 함수 read 에러 관련 질문
안녕하세요.iOS 시뮬레이터로 개발중입니다.checkToken 함수 실행 시 Unhandled Exception: PlatformException(Unexpected security result code, Code: -25300, Message: The specified item could not be found in the keychain., -25300, null) 에러가 발생하여 질문드립니다. 값이 없을 경우 nullable string이 나와야 하는데 exception이 나오는 이유가 궁금해요. 혹시 몰라서 write를 먼저 하고 read를 했더니 값이 잘 불러와집니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
restaurant_repository.g.dart 파일에서 에러가 나옵니다.
g dart 파일을 생성하면 final value = RestaurantDetailModel.fromJson(_result.data!); 여기서 에러가 발생합니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
데이터가 구조가 List<dynamic> 형식일때 질문 드려 봅니다.
[ { "no": 1, "write_date": "2015-03-25 13:28:15", "content": "쇼핑몰 공지사항\r\n 활용한 게시판입니다." }, { "no": 2, "write_date": "2015-03-25 13:28:40", "content": "쇼핑몰 오픈이벤트!\r\n쇼핑몰 오픈이벤트로 최대 30% 까지 할인된 가격으로 만나보세요." },]안녕하세요.데이터가 강의처럼 meta와 data로 나뉘고 그안에 List로 들어오는게 아니고 위에처럼 그냥 List로 들어온다면 1.Future<CursorPagination<RestaurantModel>> paginate();이렇게 사용 못하는건가요?된다면class CursorPagination<T> extends CursorPaginationBase { 여기를 어떻게 해야 할지 모르겠습니다.} 2.Future<List<RestaurantModel>> paginate(); 이렇게 사용해야 한다면abstract class CursorPaginationBase {} 이거는 어떻게 사용해야 할까요? 3.Error: type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>?' in type cast 이렇게 에러가 뜨는데CursorPagination<T> 이런 구조로 한다는거 자체가 말이 안되는건가요?Future<List<RestaurantModel>> 이런 형식으로 해야 한다면 RestaurantModel을 T로 받으면서 할 수 없는건가요?CursorPaginationBase 이거는 RestaurantModel을 class RestaurantModel extends CursorPaginationBase이런식으로 사용해야 할까요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
여러개의 Repository 에서 하나의 Dio를 공유해야 하는 이유가 뭔가요?
여러개의 Repository 에서 하나의 Dio를 공유해야 하는 이유가 뭔가요? 장점에 대해서 나중에 설명해 주신다고 하셨는데 그 내용을 못찾았어요.
-
해결됨Only Javascript만으로 배우는 하이브리드앱 패키징
onesignal 연동 확인 부탁드립니다!
window.plugins.OneSignal.setAppId("원시그널앱ID"); //푸시메세지 수신부 window.plugins.OneSignal.setNotificationOpenedHandler(function(jsonData){ console.log('notificationOpenedCallback: ' + JSON.stringify(jsonData)); }); //푸시권한 허용팝업(android OS 13 이상에서 사용됨) window.plugins.OneSignal.promptForPushNotificationsWithUserResponse(function(accepted) { console.log("User accepted notifications: " + accepted); });위 코드가 정상적으로 작동을 안하는 것 같습니다! 버전 문제인지 모르겠는데 코드 추가하고 onesignal 사이트에서 "Check Subscribed Users" 버튼을 클릭하면 아래 같은 오류가 발생하는데 확인 부탁드립니다 We haven't detected any subscribed users yet for this platform. Try some of the tips below or contact us for support.Run OneSignal.SetLogLevel(OneSignal.LOG_LEVEL.DEBUG, OneSignal.LOG_LEVEL.DEBUG);before OneSignal initialization and check your ADB logcat for errors or warnings.Verify that the OneSignal App ID is correct in your app or site.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
아이폰 배포 관련
안녕하세요 강사님~플러터로 웹뷰 이용하여 앱을 만들었는데안드로이드는 APK파일 만드는것이 간단하였으나아이폰의 IPA를 생성하려고 하니 좀 힘이듭니다.앱스토어 개발자 비용을 내지 않고는 IPA파일 생성이 전혀 불가능한지궁금합니다.xcode와 핸드폰 연결해서는 사용중인데, 내부 사용자들한테 배포를 좀 해보려고 하는데전혀 안되네요.. ㅠㅠ
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
retrofit 요청 후 상태 코드
retrofit에서 요청 후에 인터셉터 이후에 UI 단에서 스낵바를 띄우기 위해 상태코드를 반환 받고 싶은데 Response타입을 반환 시키려면 파일을 임의로 수정해주어야 하더라고요 혹시 다른 방법이 있을까요? @POST('/reservation') @Headers({'Authorization': 'true'}) Future<Response<dynamic>> makeReservation({ @Body() required ReservationRequestModel reservation, }); child: IconButton( onPressed: () { reservationRepositoryProvide .getRepository() .makeReservation( reservation: ReservationRequestModel( meetingRoomId: widget.room.id, reservationStartTime: selectReservationTimeData .selectedFullDate.value! .add(1.seconds), reservationEndTime: selectReservationTimeData .selectedFullDate.value! .add(const Duration(hours: 1)), members: 4, )) .then((res) { if (res.statusCode == 201) { context.showSnackbar( message: '예약 완료 되었습니다. 마이페이지에서 확인해주세요', isSuccess: true); } else { final errorMessage = res.data['errorMessage']; context.showSnackbar( message: errorMessage, isSuccess: false); } }); }, 현재 상태 코드를 반환 시켜 스낵바를 띄워주는데 아래처럼 retrofit 코드를 임의로 고쳐야하네요 @override Future<Response<dynamic>> makeReservation( {required ReservationRequestModel reservation}) async { const _extra = <String, dynamic>{}; final queryParameters = <String, dynamic>{}; final _headers = <String, dynamic>{r'Authorization': 'true'}; _headers.removeWhere((k, v) => v == null); final _data = <String, dynamic>{}; _data.addAll(reservation.toJson()); final _result = await _dio .fetch<Map<String, dynamic>>(_setStreamType<Response<dynamic>>(Options( method: 'POST', headers: _headers, extra: _extra, ) .compose( _dio.options, '/reservation', queryParameters: queryParameters, data: _data, ) .copyWith( baseUrl: _combineBaseUrls( _dio.options.baseUrl, baseUrl, )))); // final value = Response<dynamic>.fromJson(_result.data!); return _result; }다른 방법이 있을까요?