en un clic
dio-retrofit
Dio+Retrofit API 통신 패턴. API 클라이언트, 인터셉터 작성 시 자동 참조.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
Dio+Retrofit API 통신 패턴. API 클라이언트, 인터셉터 작성 시 자동 참조.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Figma MCP 로 디자인을 픽셀 퍼펙트로 구현. 토큰 추출 → 테마 매핑 → 정확한 레이아웃 → 스크린샷 diff 검증 루프. Flutter/React 공용.
Flutter 외부/BaaS API 연동 풀 파이프라인. 계약 → retrofit 코드젠 → dio 인터셉터(auth/retry) → Result 매핑 → repository → 오프라인 캐시. dio-retrofit 스킬 심화.
TanStack Query v5 패턴 (React). 서버 상태 패칭/캐시/뮤테이션/낙관적 업데이트.
Supabase BaaS 연동 패턴 (Flutter). 인증/DB/리얼타임/스토리지/RLS.
Auth.js v5 (NextAuth) 패턴. 세션/미들웨어/Server Action 가드.
Cross-stack API 계약 동기화. NestJS DTO ↔ Flutter DTO 일치성 관리.
Basé sur la classification professionnelle SOC
| name | dio-retrofit |
| description | Dio+Retrofit API 통신 패턴. API 클라이언트, 인터셉터 작성 시 자동 참조. |
| globs | client/**/remote/**, client/**/network/**, client/**/api/** |
이 프로젝트는 dio + retrofit만 사용한다. http 패키지 사용 금지 (코딩 스타일 룰).
dependencies:
dio: ^5.9.2
retrofit: ^4.9.2
json_annotation: ^4.11.0
dev_dependencies:
retrofit_generator: ^10.2.5
json_serializable: ^6.13.1
build_runner: ^2.14.1
추가 후:
flutter pub get
dart run build_runner build --delete-conflicting-outputs
@riverpod
Dio dio(Ref ref) {
final dio = Dio(BaseOptions(
baseUrl: Environment.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
));
dio.interceptors.addAll([
AuthInterceptor(ref),
ApiLogInterceptor(), // logger 패키지 기반 (print 금지)
]);
return dio;
}
class AuthInterceptor extends Interceptor {
final Ref _ref;
AuthInterceptor(this._ref);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = _ref.read(authTokenProvider);
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
try {
final newToken = await _ref.read(authProvider.notifier).refresh();
if (newToken != null) {
err.requestOptions.headers['Authorization'] = 'Bearer $newToken';
final response = await _ref.read(dioProvider).fetch(err.requestOptions);
return handler.resolve(response);
}
} catch (e, st) {
logger.e('Token refresh failed', error: e, stackTrace: st);
}
// refresh 실패 → 로그아웃
_ref.read(authProvider.notifier).logout();
}
handler.next(err);
}
}
@RestApi()
abstract class BoardApi {
factory BoardApi(Dio dio) = _BoardApi;
@GET('/boards')
Future<List<BoardDto>> getBoards();
@GET('/boards/{boardId}')
Future<BoardDetailDto> getBoardDetail(@Path() String boardId);
@POST('/board/roll')
Future<RollResultDto> rollDice();
@POST('/board/buy')
Future<BuyResultDto> buyLand(@Body() BuyLandRequest request);
}
class BoardRepositoryImpl implements BoardRepository {
final BoardApi _api;
BoardRepositoryImpl(this._api);
@override
Future<Result<List<Board>>> getBoards() async {
try {
final dtos = await _api.getBoards();
return Result.success(dtos.map((d) => d.toEntity()).toList());
} on DioException catch (e, st) {
logger.e('getBoards failed', error: e, stackTrace: st);
return Result.failure(e.toAppError());
} on FormatException catch (e, st) {
logger.e('getBoards parse error', error: e, stackTrace: st);
return Result.failure(AppError.parse(message: e.message, stackTrace: st));
}
}
}
catch (e, st))dart run build_runner build --delete-conflicting-outputs