[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
void paginate({ int fetchCount = 20, // 추가로 데이터 더 가져오기 // true = 더 가져오기 // false = 새로고침 bool fetchMore = false, // 강제로 다시 로딩하기 // true - CursorPaginationLoading() bool forceRefetch = false, }) async { // 5가지 가능성 // State의 상태 // 1) CursorPagination - 정상적으로 데이터가 있는 상태 // 2) CursorPaginationLoading - 데이터 로딩중(현재 캐시 없음 // 3) CursorPaginationError = 에러가 있는 상태 // 4) CursorPaginationRefetching = 첫번째 페이지부터 다시 데이터를 가져올때 // 5) CursorPaginationFetchMore - 추가 데이터를 paginate 해오라는 요청을 받았을때 // 바로 반환하는 상황 // 1) hasMore == false(기존 상태에서 이미 다음 데이터가 없다는 값을 들고 있다면) // 2) 로딩중 - fetchMore : true // fetchMore : false - 새로고침의 의도가 있음. try { if (state is CursorPagination && !forceRefetch) { final pState = state as CursorPagination; if (!pState.meta.hasMore) { return; } } final isLoading = state is CursorPaginationLoading; final isRefetching = state is CursorPaginationRefetching; final isFetchingMore = state is CursorPaginationFetchingMore; if (fetchMore && (isLoading || isRefetching || isFetchingMore)) { return; } // PaginationParams 생성 PaginationParams paginationParams = PaginationParams( count: fetchCount, ); // fetchMore if (fetchMore) { final pState = state as CursorPagination; state = CursorPaginationFetchingMore( meta: pState.meta, data: pState.data, ); paginationParams = paginationParams.copyWith( after: pState.data.last.id, ); } final resp = await repository.paginate( paginationParams: paginationParams, ); if (state is CursorPaginationFetchingMore) { final pState = state as CursorPaginationFetchingMore; // 기존 데이터에 새로운 데이터 추가 state = resp.copyWith(data: [ ...pState.data, ...resp.data, ]); } else { state = resp; } } catch (e) { state = CursorPaginationError(message: '데이터를 가져오지 못했습니다.'); } } 이부분이 있는데 왜 하위의 fetchMore의 else문에서 또 조건을 걸어놓는지 궁금합니다. // fetchMore if (fetchMore) { final pState = state as CursorPagination; state = CursorPaginationFetchingMore( meta: pState.meta, data: pState.data, ); paginationParams = paginationParams.copyWith( after: pState.data.last.id, ); } // 데이터를 처음부터 가져오는 상황 else { // 만약 데이터가 있는 상황이라면 기존 데이터 보존한 채로 Fetch(API 요청)을 진행 if (state is CursorPagination && !forceRefetch) { final pState = state as CursorPagination; state = CursorPaginationRefetching( meta: pState.meta, data: pState.data, ); } else { state = CursorPaginationLoading(); } }
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
강의 보면 로그인 스크린의 빌드 컨텍스트 내부에 storage를 선언하셨는데, class _LoginScreenState extends State<LoginScreen> { String user = ""; String password = ""; @override Widget build(BuildContext context) { final storage = FlutterSecureStorage(); final dio = Dio(); final emulatorIp = '10.0.2.2:3000'; final simulatorIp = '127.0.0.1:3000'; final ip = Platform.isIOS ? simulatorIp : emulatorIp; return ...; } } 이 부분에서 왜 빌드컨텍스트 내부에 작성했는지 궁금합니다.
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
final List<Map<String,dynamic> products; 위와 같이 진행한다면 factory RestaurantDetailModel.fromJson({ required Map<String, dynamic> json, }) { return RestaurantDetailModel( id: json['id'], name: json['name'], thumbUrl: 'http://$ip${json['thumbUrl']}', tags: List<String>.from(json['tags']), priceRange: RestaurantPriceRange.values.firstWhere( (element) => element.name == json['priceRange'], ), ratings: json['ratings'], ratingsCount: json['ratingsCount'], deliveryTime: json['deliveryTime'], deliveryFee: json['deliveryFee'], detail: json['detail'], products: List<Map<String, dynamic>>.from(json['products']), ); } fromJson 도 위와 같이 변경될 것 같은데, 이런 방식은 비효율적이거나 권장되지 않는 방식인가요? 객체 안의 객체를 따로 모델링 하는 이유는 객체 안의 객체를 인스턴스화해서 재사용하는데 목적이 있는 건가요? 수업때 설명한 방식은 List 안에 Map 형태의 키값이 정해지지 않아 데이터 추출이 번거로워질 수 있다고 생각하면 되나요?
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
강의를 진행하던 도중 재실행을 했더니 갑작스럽게 E/flutter ( 7367): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [connection timeout]: The request connection took longer than 0:00:00.000000 and it was aborted. To get rid of this exception, try raising the RequestOptions.connectTimeout above the duration of 0:00:00.000000 or improve the response time of the server. E/flutter ( 7367): Error: SocketException: Connection timed out (OS Error: Connection timed out, errno = 110), address = 192.168.1.106, port = 39308 E/flutter ( 7367): #0 DioMixin.fetch (package:dio/src/dio_mixin.dart:519:7) E/flutter ( 7367): <asynchronous suspension> E/flutter ( 7367): #1 LoginScreenState.build.<anonymous closure> (package:delivery/user/view/loginscreen.dart:77:36) E/flutter ( 7367): <asynchronous suspension> E/flutter ( 7367): dio의 연결초과 에러라고 뜨는데 해결방법을 모르겠습니다. 잘 진행하다가 갑자기 이렇게 되버려서... 77번째줄 코드는 아래와 같습니다. final resp = await dio.post( 'http://ip/auth/login', options: Options( headers: { 'authorization': 'Basic $token', }, ), );
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
안녕하세요. flutter 중급 강의를 들으면서 궁금한 점이 생겨서 질문 드립니다. 지금까지 수많은 변수명들을 작성해 왔는데요. 그중에서 data라는 변수명들이이 클래스마다 여러곳에 배치되어 있는 느낌을 받았습니다. 그래서 헷갈려하지 않기 위해 앞의 약자를 덧붙여 자신만의 변수명을 만들어 왔습니다. ex) final List<T> data; --> final List<T> cpData; 하지만 [pagination params추가하기] 강의를 듣는 도중에 테스트를 해보니 E/flutter (21276): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'List<dynamic>' in type cast 오류가 발생하였고 혹시 몰라서 data를 원래대로 되돌리고 실행해 보았는데 정상적으로 작동하였습니다. 변수명을 바꿀뿐이였는데 타입을 NULL로 인식하는 이유가 궁금합니다. (콘솔창에 flutter pub run build_runner watch 실행중이였습니다.)
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
안녕하세요! 안드로이드 스튜디오에서 apk를 릴리즈용으로 만들어서 실행시킬때는 정상 동작을 확인했는데요, playstore에 올렸을때는 정상 동작을 안하는 버그가 있습니다. 어디서부터 뭐가 잘못됬는지를 알수가 없어서 질문 올립니다 ㅜㅜ 현재 playstore에 앱 번들을 업로드 한 상태입니다. 여기서 다운로드해서 실행 시 이런 스플레시 화면에서 안넘어가는데 도무지 어디서부터 체크해야할지 모르겠어서 답답한 상황입니다.. 혹시 이런 경우를 보셨다면 말씀주시면 정말 도움이 될 것 같습니다. 감사합니다 이유는 알수없지만 redirectLogic 혹은 ref.listen<UserModelBase?>(userMeProvider, (previous, next) { if (previous != next) { notifyListeners(); } }); 요게 잘 안되고있는것 같은데.. 어렵네요.. 초기 페이지를 login으로 하면 잘됩니다 ㅜ
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
안녕하세요~ 강의를 다 듣고 나니 질문이 있습니다 login screen의 상태관리에 대한 질문인데요! id, pw를 강의에서는 그냥 String username = ''; String password = ''; 로 View내부에 선언하고 TextField의 onChanged를 통해서 저 값들을 변경하고 ref .read(userMeProvider.notifier) .login(username: username, password: password); 이런식으로 넘겨주었는데, username, password 값도 상태관리를 하려면 또 이거에 관련된 프로바이더를 만들어야 하는건가요?? 추가로 id, pw값에 따라 button을 enabled - disabled 시키려면 또 이거와 관련된 프로바이더를 추가로 만들어야 하는건가요?? 이런식으로 하면 한 화면에서도 프로바이더가 굉장히 많이 만들어지게 되는데 이런 방식이 맞는지 궁금합니다.. 또 그 화면에서만 쓰는데도 전역으로 프로바이더들이 굉장히 많이 선언될거같아요.. 강의에서는 되게 굵직굵직한 data들을 전역 provider로 만들어서 굉장히 편했는데, 앱을 개발하다보면 그 화면에서만 필요한 데이터들도 많을거고 단순히 버튼 상태처럼 bool값도 엄청 많을텐데 추후 테스트를 위해서 로직을 분리하려면 이런것도 싹 다 프로바이더로 만들어야 하는것인가요..?
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
리프레시 토큰이 만료될 때 바로 login screen으로 이동하는 것이 아니라 다이얼로그를 띄운 후 확인 버튼을 누르면 화면 이동을 하고 싶은데 이 showDialog를 어디서 해야 할지 난관에 빠졌습니다. 현재는 리프레시가 만료되면 dio interceptor에서 authProvider의 logout() 을 통해 userMeProvider의 logout()을 호출해 state를 null로 만들고, goRouter가 state가 null이면 바로 로그인 스크린으로 redirect 시키고 있는데 이 흐름이 뷰에서 이루어지는 것이 아니라서 context가 필요한 showDialog를 어느 부분에서 구현해야 할지 감이 안 잡힙니다. state를 null로 만들기 전에 다이얼로그를 띄우려면 AuthProvider에서 userMeProvider의 logout를 호출하기 전에 실행되어야 할 것 같기도 한데 logout() { // showDialog()??? ref.read(userMeProvider.notifier).logout(); } 결국 view단에서 호출하는 것이 아니기 때문에 context를 얻어올 방법이 떠오르지 않네요...
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
안녕하세요. 코팩님 초급 강의 듣다가 이제 중급으로 과감히 넘어온 수강생입니다 ! 첫번째 챕터를 하면서 styleFrom() 에서 막혔는데, 질문들 훑어 보고 한 번 해결해 봤는데 이대로 강의에 적용하면 되나요? ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( foregroundColor: PRIMARY_COLOR, ), child: Text('로그인'), ), TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: Colors.black, ), child: Text('회원가입'), ), 그리고, IOS랑 Android는 버튼 색감 자체가 차이가 있는 거기 때문에 색감 같은거는 앞으로 감안하면서 들으면 되는 건지요 ?? ㅎㅎ
[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
안녕하세요. "Skeleton 사용하기"의 강의 내용을 구현하고 있습니다. pubspec.yaml dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 dio: ^5.5.0+1 flutter_secure_storage: ^9.2.2 json_annotation: ^4.9.0 retrofit: '>=4.0.0 <5.0.0' logger: any #for logging purpose flutter_riverpod: ^2.5.1 riverpod_annotation: ^2.3.5 skeletons: ^0.0.3 restaurant_detail_screen.dart에서 import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:skeletons/skeletons.dart'; return DefaultLayout( title: '불타는 떡볶이', child: CustomScrollView( slivers: [ renderTop( model: state, ), renderLoading(), // if (state is RestaurantDetailModel) renderLabel(), // if (state is RestaurantDetailModel) // renderProducts( // products: state.products, // ), ], ), ); } // SkeletonParagraph Logic SliverPadding renderLoading() { return SliverPadding( padding: EdgeInsets.symmetric( horizontal: 16.0, ), sliver: SliverList( delegate: SliverChildListDelegate( List.generate( 3, (index) => SkeletonParagraph( style: SkeletonParagraphStyle( lines: 5, ), ), ), ), ), ); } 을 구현하고 재실행하면 다음과 같은 에러가 발생하고 있습니다. Skeleton용하기 이미지가 작아서 텍스트로 붙여넣었습니다. Launching lib\main.dart on sdk gphone64 x86 64 in debug mode... Running Gradle task 'assembleDebug'... ../../../../AppData/Local/Pub/Cache/hosted/pub.dev/skeletons-0.0.3/lib/src/widgets.dart:56:42: Error: The getter 'backgroundColor' isn't defined for the class 'ThemeData'. - 'ThemeData' is from 'package:flutter/src/material/theme_data.dart' ('/C:/flutter/packages/flutter/lib/src/material/theme_data.dart'). Try correcting the name to the name of an existing getter, or defining a getter or field named 'backgroundColor'. color: Theme.of(context).backgroundColor, ^^^^^^^^^^^^^^^ ../../../../AppData/Local/Pub/Cache/hosted/pub.dev/skeletons-0.0.3/lib/src/widgets.dart 💯 46: Error: The getter 'backgroundColor' isn't defined for the class 'ThemeData'. - 'ThemeData' is from 'package:flutter/src/material/theme_data.dart' ('/C:/flutter/packages/flutter/lib/src/material/theme_data.dart'). Try correcting the name to the name of an existing getter, or defining a getter or field named 'backgroundColor'. color: Theme.of(context).backgroundColor, ^^^^^^^^^^^^^^^ Target kernel_snapshot failed: Exception 여러가지로 찾아보았지만, 해결되지 않았습니다. 이 문제에 대한 해결 방법에 대해 질문을 드립니다. 감사합니다.