μ©λ
μλ²μ ν΅μ νκΈ° μν¨μ΄λ€. ν΅μ μ ν λ κΈ°λ³Έμ μΌλ‘ ν€λμ μ€μ΄μ μ λ¬ν΄μΌν μ 보λ€λ μκ³ , μΈν°μ
ν°λ₯Ό νμ©ν΄μ μλ²μ μλ΅μ λ°λΌ μ μ²λ¦¬κ° νμν κ²½μ°λ μλ€. μ΄ λͺ¨λ μ¬νμ λμνκΈ° μν λͺ¨λμ΄λ€.
ꡬν
λ₯Ό μ¬μ©νλ€.
$ flutter pub add dio
data λ μ΄μ΄ λ΄μ network λ§κ³ response, request, repository λ₯Ό λ μ μλ€. λμ€μ domain μμμμλ μ 리νκ² μ§λ§ repository μ κ²½μ° domain λ μ΄μ΄μ μΈν°νμ΄μ€λ₯Ό λκ³ data λ μ΄μ΄μμ ꡬν체λ₯Ό λ§λ€μ΄μ μ¬μ©νλ κ²μ΄ μ격νκ² ν΄λ¦° μν€ν
μ²λ₯Ό ꡬννλ κ²μ΄λ€. νμ§λ§ νλ‘μ νΈλ₯Ό ν΄λ³΄λ λ°μ΄ν° μμ± μ λ΅μ΄ μ 체μ μΌλ‘ λ°λλ€λκ° νμ§ μλ μ΄μ λΆνμνκ² κ³μΈ΅μ λλλ νμλΌκ³ λκ»΄μ‘λ€. κ²°λ‘ μ μΌλ‘ repository λ data λ μ΄μ΄μλ§ λ°λ‘ ꡬν체λ₯Ό λλ κ²μ΄ μ’μ κ² κ°λ€.
import 'base_http_client.dart';
class ApiClient extends BaseHttpClient {
ApiClient({
required super.clientBaseUrl,
required super.customInterceptors,
super.customOptions,
});
}
import 'package:dio/dio.dart';
import '../../../application/environments/environment.dart';
abstract class BaseHttpClient with DioMixin implements Dio {
final String clientBaseUrl;
final List<Interceptor> customInterceptors;
BaseOptions? customOptions;
BaseHttpClient({
required this.clientBaseUrl,
required this.customInterceptors,
this.customOptions,
}) : super() {
if (customOptions != null) {
options = customOptions!;
}
httpClientAdapter = HttpClientAdapter();
options = BaseOptions(
baseUrl: clientBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
);
if (Environment.isDevelopment) {
interceptors.add(LogInterceptor(requestBody: true, responseBody: true));
}
interceptors.addAll(customInterceptors);
}
}
μμ κ°μ΄ μμ
ν΄μ£Όλ©΄ ν΄λΌμ΄μ΄μΈνΈ μ μλ λμ΄ λλ€. μλλ λ΄κ° μ¬μ©ν μΈν°μ
ν°λ€. κΈ°λ³Έμ μΈ κ²λ€μ΄λ€.
BaseHeaderInterceptor λ ν€λμ κΈ°λ³Έμ μΌλ‘ λ΄μ λ΄μ©λ€μ μΈν
νλ€.
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
// import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../../barrel.dart';
class BaseHeaderInterceptor extends InterceptorsWrapper {
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
await _setHeaderWithAccessToken(options.headers);
await _setHeaderWithPackageInfo(options.headers);
await _setHeaderWithDeviceInfo(options.headers);
// await _setHeaderWithPushToken(options.headers);
super.onRequest(options, handler);
}
Future<void> _setHeaderWithAccessToken(Map<String, dynamic> headers) async {
final String? accessToken = await AccessManager.getAccessToken();
if (accessToken == null) {
return;
}
const String ACCESS_TOKEN_PREFIX = 'Bearer';
const String SPACE = ' ';
headers['access-token'] = ACCESS_TOKEN_PREFIX + SPACE + accessToken;
}
Future<void> _setHeaderWithPackageInfo(Map<String, dynamic> headers) async {
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
headers['app-name'] = packageInfo.appName;
headers['package-name'] = packageInfo.packageName;
headers['version'] = packageInfo.version;
headers['build-number'] = packageInfo.buildNumber;
}
Future<void> _setHeaderWithDeviceInfo(Map<String, dynamic> headers) async {
final DeviceInfoPlugin deviceInfo = Injector.deviceInfoPlugin;
if (Platform.isAndroid) {
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
headers['uuid'] = androidInfo.id;
headers['operating-system'] = 'Android';
headers['operating-system-version'] =
androidInfo.version.sdkInt.toString();
return;
}
if (Platform.isIOS) {
final IosDeviceInfo iOSInfo = await deviceInfo.iosInfo;
headers['uuid'] = iOSInfo.identifierForVendor;
headers['operating-system'] = 'iOS';
headers['operating-system-version'] = iOSInfo.systemVersion;
}
}
// Future<void> _setHeaderWithPushToken(Map<String, dynamic> headers) async {
// String? pushToken = await FirebaseMessaging.instance.getToken();
// headers['push-token'] = pushToken ?? '';
// }
}
TokenInterceptor λ ν ν° λ§λ£μ λ°λ‘ μλ¬λ₯Ό λ΄λ κ²μ΄ μλλΌ λ¦¬νλ μ ν ν°μ μ΄μ©ν΄μ λ€μ ν ν°μ μ¬λ°κΈλ°μμ κΈ°μ‘΄μ μμ²μ κ·Έλλ‘ μννλ κ²μ΄λ€. μλ²μμ ν ν°μ΄ λ§λ£μΌλ μλμ κ°μ΄ μ½μλ μ½λμ statusλ₯Ό λ΄λ €μ£Όκ³ λ μ‘°κ±΄μ΄ λͺ¨λ λ§μ‘±ν λ μ¬λ°κΈ μ²λ¦¬λ₯Ό νλ€. λλ μλ²μμλ 리νλ μ ν ν°μ 보κ΄νλλ‘ ν΄μ 보μμ κ°ν νλ μͺ½μΌλ‘ ꡬννλ€.
import 'package:dio/dio.dart';
import '../../../barrel.dart';
class TokenInterceptor extends InterceptorsWrapper {
TokenInterceptor();
@override
Future<void> onError(
DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 &&
err.response?.data['code'] == 'AU01') {
final String? refreshToken = await AccessManager.getRefreshToken();
if (refreshToken != null) {
try {
Response response = await Injector.apiClient
.put('/user/token/refresh?refreshToken=$refreshToken');
final String newAccessToken = response.data['accessToken'];
final String newRefreshToken = response.data['refreshToken'];
await AccessManager.replaceTokens(
accessToken: newAccessToken, refreshToken: newRefreshToken);
// μλμ μμ²μ μ¬μ€ν
RequestOptions requestOptions = err.requestOptions;
requestOptions.headers['access-token'] = 'Bearer $newAccessToken';
Response newResponse = await Injector.apiClient.request(
requestOptions.path,
queryParameters: requestOptions.queryParameters,
options: Options(
method: requestOptions.method,
headers: requestOptions.headers,
responseType: requestOptions.responseType,
contentType: requestOptions.contentType,
extra: requestOptions.extra,
),
data: requestOptions.data,
);
handler.resolve(newResponse);
} catch (exception) {
// ν ν° μ¬λ°κΈ μ€ν¨μ λ‘μ§ μ²λ¦¬
handler.next(err);
}
}
} else {
handler.next(err);
}
}
}
μλλ μμ€ν
μ κ²μ€μΌλ μμ€ν
μ κ²μ€μ΄λΌλ λ€μ΄μΌλ‘κ·Έλ₯Ό λμ°κ³ μ νμ§ μμ΄ μ±μ μ’
λ£μν€λλ‘ νλ μΈν°μ
ν°μ΄λ€. ν ν° μΈν°μ
ν°μ λ§μ°¬κ°μ§λ‘ μλ²μμ μ μλ μ½λμ status λ₯Ό λ΄λ €μ£Όκ² νλ€.
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import '../../../barrel.dart';
class ServerMaintenanceInterceptor extends Interceptor {
ServerMaintenanceInterceptor();
@override
Future<void> onError(
DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 503 &&
err.response?.data['code'] == 'SY01') {
BasicDialog.show(
context: RouterConfiguration.navigatorKey.currentContext!,
title: TextManager.serviceMaintenanceTitle.tr(),
contents: TextManager.serviceMaintenanceMessage.tr(),
confirmAction: () {
exit(0);
});
} else {
handler.next(err);
}
}
}
μ¬μ©
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter_template/barrel.dart';
import 'package:get_it/get_it.dart';
class Injector {
Injector._();
static DeviceInfoPlugin get deviceInfoPlugin =>
GetIt.instance.get<DeviceInfoPlugin>();
static Dio get apiClient => GetIt.instance.get<Dio>();
static Future registerDependencies() async {
_registerUtils();
_registerNetworks();
_registerRepositories();
}
static _registerUtils() async {
GetIt.instance
.registerLazySingleton<DeviceInfoPlugin>(() => DeviceInfoPlugin());
}
static _registerNetworks() async {
GetIt.instance.registerLazySingleton<Dio>(
() => ApiClient(
clientBaseUrl: Environment.baseUrl,
customInterceptors: [
BaseHeaderInterceptor(),
// TokenInterceptor(),
],
),
);
}
static _registerRepositories() async {}
}
μ¬μ©μ μμ κ°μ΄ Injector μ μ μν΄λκ³ νμν λ(= Bloc μ μ£Όμ
λλ Repository μ μ£Όμ
) κΊΌλ΄μ μ¬μ©νλ€.