묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
Riverpod + Drift MVVM패턴 오류
안녕하세요 선생님!초급, 중급 강의 들으면서 riverpod 와 drift를 사용해서 MVVM패턴으로 간단한 코드를 작성 하였는데 에러가 발생하여 질문 드립니다._onTap으로 생성을 시도하면 point2번까지는 값이 잘 전달되는데 다음과 같은 오류가 발생합니다. ##### 오류 내용 #####[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: SqliteException(1299): while executing statement, NOT NULL constraint failed: counter_model.value, constraint failed (code 1299)Causing statement: INSERT INTO "counter_model" ("title") VALUES (?), parameters: TextName 답변 부탁 드리겠습니다.감사합니다!
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
type 'Null' is not a subtype of type 'List<dynamic>' in type cast
섹션 11 > Pagination 일반화하기 > RatingsPagination 렌더링하기 수강 중인데 "type 'Null' is not a subtype of type 'List<dynamic>' in type cast"라서 RatingCard가 나오지 않는데 어디서 부터 확인해야할 지 모르겠습니다... ㅠㅠflutter: [RES] [GET] http://127.0.0.1:3000/restaurant/5ac83bfb-f2b5-55f4-be3c-564be3f01a5b/rating/?count=20flutter: type 'Null' is not a subtype of type 'List<dynamic>' in type castflutter: #0 $RatingModelFromJson (package:actual/rating/model/ratingmodel.g.dart:14:62)#1 new RatingModel.fromJson (package:actual/rating/model/rating_model.dart:27:62)#2 RestaurantRatingRepository.paginate.<anonymous closure> (package:actual/restaurant/repository/restaurantrating_repository.g.dart:46:29)#3 MappedListIterable.elementAt (dart:_internal/iterable.dart:415:31)#4 ListIterator.moveNext (dart:_internal/iterable.dart:344:26)#5 new GrowableList.ofEfficientLengthIterable (dart:core-patch/growable_array.dart:189:27)#6 new GrowableList.of (dart:core-patch/growablearray.dart:150:28)#7 new List.of (dart:core-patch/array_patch.dart:47:28)#8 ListIterable.toList (dart:_internal/iterable.dart:214:7)#9 $CursorPaginationModelFromJson (package:actual/common/model/cursorpagination_model.g.dart:15:60)#10 new CursorPaginationModel.fromJson (package:actual/common/model/cursor_pagination_model.dart:44:108)#11 _RestaurantRatin<…>flutter: Instance of 'CursorPaginationModelError'
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
dio HTTP 요청 결과 처리
안녕하세요! dio 활용한 http 요청 처리에 대해 질문이 있습니다. 해당 프로젝트의 경우 http 요청의 response body에 맞는 model을 jsonSerializable로 생성하고, 해당 model을 return type으로 갖는 함수를 repository에 선언하여 response body를 model.fromJson 형태로 가져오는 걸로 이해했습니다. 하지만 이 경우 response body가 기존에 선언한 model의 형태와 동일한 경우에만 fromJson으로 받아올 수 있고, 다른 에러 코드 등(400 BAD_REQUEST)에 의해 response body가 다른 형태로 오게 된다면 처리가 불가능 할 것 같습니다. 이런 경우 프론트엔드에서 요청 처리를 어떻게 진행해야 하나요? repository의 함수에서 response.data 만 return 해주는 경우 해당 함수를 호출하는 다른 provider에서는 예외 처리가 어려울 것 같아서 질문드립니다!
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
RestaurantDetail 캐싱
Restaurant 와 RestaurantDetail 가 서로 연결될 수 있게 설계를 했기 때문에, detail 캐싱이 가능한걸까요?현업에서 강의와 다르게 설계가 되어 있다면 별도의 상태를 하나 더 만들어야 할까요?==== 억지 가정 ====현업에서Restaurant 의 이미지는 thumbUrl 로, RestaurantDetail 의 이미지는 detailThumbUrl 로 설계가 되어 있다는 가정
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
만료된 refreshToken으로 요청할때의 문제, try catch가 작동하지 않는듯?
void checkToken() async { final refreshToken = await storage.read(key: REFRESH_TOKEN_KEY); final accessToken = await storage.read(key: ACCESS_TOKEN_KEY); final dio = Dio(); try { final resp = await dio.post( 'http://$ip/auth/token', options: Options( headers: { 'authorization': 'Bearer $refreshToken', }, ), ); await storage.write( key: ACCESS_TOKEN_KEY, value: resp.data['accessToken']); // if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (_) => const RootTab(), ), (route) => false); } catch (e) { print('e: $e'); // if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (_) => const LoginScreen(), ), (route) => false); // 뒤로가기 버튼을 눌러도 다시 로그인 화면으로 돌아가지 않음 } }이 로직이 원래 잘됬는데 몇일 사용안하다가 다시키니까 dio.post 부분에서 401에러가 발생하는데이는 만료된 refreshToken으로 요청해서 발생하는 에러로 예상했습니다.그런데 try catch로 감쌋기때문에 catch아래 로그인스크린으로 이동하는 로직이 실행되어야 한다고 생각됬는데예상과 다르게 그냥 앱은 먹통이되고 dio 패키지 내부로 이동되며 401에러를 표시합니다.임시로 로그인스크린을 강제로 띄워서 다시 토큰을 발행해서 해결했습니다만왜 이런 상황이 발생하는지 잘이해가 되지않아서 질문드립니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
authProvider로 루트설정시에, 스플래시화면에서 일정작업을 수행하고싶으면 어떻게 해야하나요?
authProvider로 루트 설정하는거 정말 최고예요..!!항상 좋은 강의에 감사드립니다!강의 듣다가 하나 궁금한 점이 있는데, 보통 스플래시 화면에서 데이터를 미리 불러온다던지 일부러 1-2초 일정한 딜레이를 건다거나 스플래시에 움직이는 애니메이션을 넣는 경우에는 이런 authProvider에서 어떻게 해결해야하나요? redirectLogic 안에서 isInitialized라고 지정해둔 변수를 이용해서 해봤는데 이게 맞는지ㅠ 좀 더러운거같아서 여쭤봅니다!Future<String?> redirectLogic( BuildContext context, GoRouterState state) async { if (!isInitialized) { isInitialized = true; return '/splash'; } if (isInitialized) { await Future.wait([ Future.delayed(const Duration(seconds: 1)), // 1초 지연 ]); } final UserBase? user = await ref.read(userProvider); // print('redirectLogic - user : ${user.toString()}'); final loggingIn = state.location == '/login'; // 유저 정보가 없는데 // 로그인중이면 그대로 로그인 페이지에 두고 // 만약에 로그인중이 아니라면 로그인 페이지로 이동 if (user == null) { return loggingIn ? null : '/login'; } if (user is UserModel && loggingIn || state.location == '/splash') { // 여기서 미리 가져오면 좋을 데이터를 가져오기 } // user가 null이 아님 // UserModel // 사용자 정보가 있는 상태면 // 로그인 중이거나 현재 위치가 SplashScreen이면 // 홈으로 이동 if (user is UserModel) { return loggingIn || state.location == '/splash' ? '/' : null; } // UserModelError if (user is UserError) { return !loggingIn ? '/login' : null; } return null; }
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
중급반 섹션6 <Dio onError Interceptor 작업하기> 질문입니다.
중급반 섹션6 <Dio onError Interceptor 작업하기>강의 8분 정도에 onError작업하는 과정에서에러사항 처리하는 과정에서refresh토큰으로 accessToken 요청경로가 아래 같은 경로로 설정하셨는데,final isPathRefresh = err.requestOptions.path == '/auth/token'; restaurnatRepository 에서 baseUrl을 이렇게 설정하셨는데 강의처럼 설정하면 http로 시작안하면 baseUrl이 온다고 나와있어서'http://$ip/restaurant'/// If the `path` starts with 'http(s)', the `baseURL` will be ignored, otherwise, /// it will be combined and then resolved with the baseUrl.'http://$ip/restaurant/auth/token'이 이렇게 되는게 아닌지 궁금합니다. +그리고 Riverpod pdf자료 추가 안되어있는거 같은데 이것도 확인한번부탁드릴게요~ㅎ
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
StateNotifierProvider 관련
강의 3:53초에 StateNotifierProvider의 제네릭에 <PaginationProvider, CursorPaginationBase>를 넣으셨는데 왜 PaginationProvider를 넣으셨는지 이해가 안돼요. 레스토랑 프로바이더 부분에서는 첫번째 제네릭으로 RestaurantStateNotifier가 들어가는데, ListView 일반화하는 코드에서도 Provider가 아니라 Notifier가 들어가야 하지 않나요???final restaurantProvider = StateNotifierProvider<RestaurantStateNotifier, CursorPaginationBase>( (ref) { final repository = ref.watch(restaurantRepositoryProvider); final notifier = RestaurantStateNotifier(repository: repository); return notifier; }, );
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
run
RiverPod ConsumerWidget 부분에 Increment(), Decrement() 를 하였을 경우 로그의 내용이 선생님의 화면처럼 명확하게 나오지 않는데...별도의 설정하는 부분이 있을까요? Update / Dispose 이런 내용이 전혀 나오지 않습니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
tag부분
원래 태그는 List<String>.from() 이런식으로 dynamic을 String으로 바꾸어줬던 것 같은데jsonSerializable에서는 List<String>으로 자동으로 바꾸어 주나요? 어떻게 String인지 아나요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
함수와 stless 클래스
안녕하세요!단어간 중간에 점을 찍는다던가, 아이콘을 텍스트와 묶어서 위젯으로 반환한다던가 하는 일을 할때,언제는 그냥 함수로 구현하고, 언제는 stless 클래스를 만들어서 build하는 형식으로 구현하는데,이 두가지 방법 중 선택하는데 기준이 있나요? 이 둘의 차이점을 크게 모르겠습니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
restaurant_repository.g.dart 에러가 발생했어요
이번 강의를 다 듣고 코드를 수정하였습니다만아래 restaurant_repository.g.dart 파일에 pagiante함수에서 queryParameters.addAll(paginationParams?.toJson() ?? <String, dynamic>{}); 이부분에 toJson이 에러가 납니다. 에러 내용은 paginationParams에 toJson 함수가 없어서 입니다.해당 강의에서 강사님 코드에서 toJson을 구현하지 않았는데 어떤 부분을 제가 놓친 것인지 모르겠습니다,@override Future<CursorPagination<RestaurantModel>> paginate( {paginationParams = const PaginationParams()}) async { const _extra = <String, dynamic>{}; final queryParameters = <String, dynamic>{}; queryParameters.addAll(paginationParams?.toJson() ?? <String, dynamic>{}); queryParameters.removeWhere((k, v) => v == null); final _headers = <String, dynamic>{r'accessToken': 'true'}; _headers.removeWhere((k, v) => v == null); final Map<String, dynamic>? _data = null; final _result = await _dio.fetch<Map<String, dynamic>>( _setStreamType<CursorPagination<RestaurantModel>>(Options( method: 'GET', headers: _headers, extra: _extra, ) .compose( _dio.options, '/', queryParameters: queryParameters, data: _data, ) .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl))); final value = CursorPagination<RestaurantModel>.fromJson( _result.data!, (json) => RestaurantModel.fromJson(json as Map<String, dynamic>), ); return value; }
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
왜 강의에 많은 부분이 생략된 것 같죠??
영상이 원래 이 분량인가요?로그인하는 부분을 구현한 적 없는데 갑자기 로그인이 구현되어 있어서 조금 당황스럽네요..ㄷㄷ혹시 제가 놓친 강의가 있나요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
로그인 방식에도 차이가 있나요?
안녕하세요 코팩님 로그인 처리시 로그인 방식에 차이가 있는지 여쭈어 보고 싶습니다. 강의에서는 refresh token과 access token을 이용한 방식을 사용하는걸로 알고 있는데요..현재 저희 회사에서 진행하고 있는 프로젝트에서 선임분께서는 다음과 같은 방식으로 로그인 코드를 작성하였는데요.. 무슨 차이인지 여쭈어보고 싶습니다.[login.Screen.dart]
-
해결됨[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
디스코드관련
안녕하세요유료강의구매시 디스코드 프리미엄채널에 추가관련해서메시지를 보냈는데 답이없으셔서 글남깁니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
Sliver 를 사용한 이유.
ListView 대신에 Sliver 를 사용하신 이유가 먼가요?어떤 걸 써도 동일한 효과인가요?
-
해결됨[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
토큰 재발급 관련 질문입니다.
안녕하세요. 기초를 수강하고, 중급 강의 절반 정도 수강한 학생입니다. 우선, 기초와 중급 모두 좋은 퀄리티의 영상과 강의를 올려주셔서 정말 감사드린다는 말씀 드립니다!현재 강의와는 별개로 따로 진행하고 있는 프로젝트에서 한 페이지에 여러 api 호출을 진행하는 과정에서 access 토큰이 만료되었을 때의 상황에서 문제를 겪고 있습니다.예를 들어, 3개의 api를 호출하여 FutureBuilder 3개 혹은 Future.wait으로 3개의 데이터를 가져오는 상황입니다.우선, 모든 요청에 access 토큰 만료를 백엔드에서 계산하고 있습니다. 만료가 되었을 때, 프론트로 401이 던져지고 onError에서 이를 캐치하여 재발급 api를 호출하고 다시 토큰을 갱신하여 secure storage에 저장하고 있습니다.(백엔드에서는 access 토큰이 만료되어 재발급 요청을 받으면, 유효한 refresh 토큰인지 확인하여 유효시 두 토큰 모두 갱신하여 재발급해줍니다.)이 과정에서 3개의 모든 api 요청에 대한 재발급을 시도하여 3번의 재발급 요청이 이루어지게 됩니다. 그리고나서 3개의 모든 api 요청에 다시 resolve를 하게 되는데, 이러한 순서의 로직이 맞는 부분인지 궁금합니다.어려움을 겪고 있는 부분에는 3개의 요청 중 2개의 요청은 요청된 시간이 밀리세컨드 단위로 다르지만, 재발급 된 토큰이 동일해 요청이 성공적으로 진행됩니다. (5개라면 2개가 성공할 때가 있고, 3개가 성공할 때가 있고 시시각각 변합니다..) 하지만, 남은 api 요청은 앞선 요청에서 이미 새롭게 토큰이 발급되었기 때문에 갱신되지 않은 토큰으로 요청을 보내게 되고, 서버에서는 토큰이 새로 갱신되었기 때문에 토큰이 유효하지 않다는 오류가 발생하게 됩니다. 여러 레퍼런스를 참고하여 QueuedInterceptorsWrapper 및 일부 코드를 추가하여 임시로 해결해놓은 상태이지만, 근본적인 원인 해결이 되지 않았다고 생각하여 질문드립니다.(여전히 5개의 요청이라면, 5개의 재발급 요청을 보내는 상황입니다...)아직 남은 강의를 모두 들어보지 않아 해결하지 못하는 문제일 수 있지만, 미리 질문부터 드리는 점 양해 드립니다,,ㅠ서둘러 완강해보도록 하겠습니다!감사합니다 :)
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
goNamed질문
GoRoute( path: 'chef/register/two', name: ChefRegisteScreen2.routeName, builder: (_,state) => ChefRegisteScreen2( //rid: 123 ) ),goNamed로 String값 말고 Map이나 리스트 자료형을 넘기는 방법이 있을까요?
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
주문페이지 리프레쉬 인디케이터 문제
안녕하세요 선생님해당 강의내용에서 당겨서 새로고침하는 리프레쉬 인디케이터를 사용하면 상단에 로딩바가 하나 도는데다시 화면이 리프레쉬되면서 화면 중앙에 로딩바가 도는 것이 2중 로딩으로 보여지는 것 같아서pagination_list_view.dart 파일내RefreshIndicator( onRefresh: () async { ref.read(widget.provider.notifier).paginate(forceRefetch: true); },위 코드의 forceRefetch: true 를 삭제하는게 더 낫겠다는 판단에RefreshIndicator( onRefresh: () async { ref.read(widget.provider.notifier).paginate(); },해당코드를 사용 하려 하는데요근데 이렇게 포스리펫치를 삭제 하게 되면[홈] 메뉴와 [음식] 메뉴에서는 정상적으로 REQ RES 포스트 겟 요청이 가는데[주문] 메뉴에서는 아무런 응답이 없어서 새로고침이 되지 않습니다. 현재 강의에서 장바구니에서 주문완료를 눌러도 장바구니 내용이 비워지지 않는 문제와 로딩이 이중으로 되는 문제를 확인했는데 장바구니 부분은 주문완료버튼을 눌렀을때 주문내용을 비워버리면 해결 될 것 같다는 개념이라도 잡히는데포스리펫치 부분은 어디서 손을 대야 될지 아직 개념이 잡히지 않는데요 홈메뉴와 음식메뉴에서는 forceRefetch:true가 아니더라도 서버에서 자료를 받는 로직이 있는데주문메뉴에서는 앱에서 발생된 주문건에 대해서만 받아오는 로직이라 그게 아닌가? 싶은 생각만 듭니다.1.앱을 처음 로딩해서 주문메뉴에 들어갔을때 -> 로딩이 강제로 되니 새로운 주문건이 화면에 표시됨2.포스리팻치 true일경우 -> 강제 새로고침기능이 작동하니 새로운 주문건이 화면에 표시됨3.포스리팻치를 삭제하고 리프레쉬인디게이터만 사용할경우 -> 새로운 주문건이 표시가 안됨. 어떤 부분을 확인하거나 살펴보는게 좋을지 힌트를 주셨으면 좋겠습니다.만일 forceRefetch:true를 꼭 사용해야 되는 설계라면로딩바 대신에 스켈레톤을 사용한다면 2중 로딩느낌이 안날 것 같다는 생각이 듭니다.
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
The instance member 'simulatorIP' can't be accessed in an initializer
simulatorIP와 emulatorIP가 빨간색 줄로 나타나며The instance member 'simulatorIP' can't be accessed in an initializer 다음과 같은 오류가 발생합니다. Stackoverflow를 통해 찾아보았으나 구체적으로 무슨 오류인지 파악하기가 어려워 질문드리고자 합니다.