network
์ฉ๋
์๋ฒ์ ํต์ ํ๊ธฐ ์ํจ์ด๋ค. ํต์ ์ ํ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํค๋์ ์ค์ด์ ์ ๋ฌํด์ผํ ์ ๋ณด๋ค๋ ์๊ณ , ์ธํฐ์ ํฐ๋ฅผ ํ์ฉํด์ ์๋ฒ์ ์๋ต์ ๋ฐ๋ผ ์ ์ฒ๋ฆฌ๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ ์๋ค. ์ด ๋ชจ๋ ์ฌํญ์ ๋์ํ๊ธฐ ์ํ ๋ชจ๋์ด๋ค.
๊ตฌํ
dio ๋ฅผ ์ฌ์ฉํ๋ค.
$ 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 ์ ์ฃผ์ ) ๊บผ๋ด์ ์ฌ์ฉํ๋ค.
Last updated