(+) Data Layer - Network

๊ฐ•์˜์—์„œ๋Š” retrofit์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ, dio ๊ฐ€ ๋” ์ธ๊ธฐ ์žˆ๊ณ  ๋ณดํŽธ์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๊ณ  ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ์ค€์œผ๋กœ ์„ค์ •ํ•œ ๊ฒƒ์„ ์ •๋ฆฌํ•œ๋‹ค.

ํŒจํ‚ค์ง€ ๊ตฌ์„ฑ ๊ด€์ ์—์„œ ๋ฐ”๋ผ๋ณธ Network

ํ•˜๋‚˜์˜ ์•ฑ์—์„œ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•  ์ผ์ด ํ”ํ•˜๊ณ , ์ด ๋•Œ ๊ฐ ์„œ๋ฒ„์™€์˜ ํ†ต์‹ ์„ ์ฑ…์ž„์ง€๋Š” ํด๋ผ์ด์–ธํŠธ๋„ ๊ตฌ๋ถ„์„ ํ•ด๋‘๋Š” ๊ฒƒ์ด ์ฝ”๋“œ ํ™•์žฅ์„ฑ์ด๋‚˜ ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ์œ ๋ฆฌํ•˜๋‹ค.

๊ทธ๋ ‡์ง€๋งŒ ํ•œํŽธ์œผ๋กœ๋Š” ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•œ๋‹ค๊ณ  ํ•ด๋„ ๊ณตํ†ต์ ์œผ๋กœ ๊ฐ€์ ธ์•ผํ•  ์—ญํ•  ๋ฐ ํ˜•ํƒœ๊ฐ€ ์žˆ์„ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ๊ณ ๋ คํ•ด์„œ base_http_client ์„ ๋ถ„๋ฆฌํ•ด๋‘์—ˆ๊ณ  ๊ฐ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด๋ฅผ ์ƒ์†ํ•ด์„œ ๊ตฌํ˜„ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

interceptor ๋„ ์šฉ๋„๋ณ„๋กœ ์ตœ๋Œ€ํ•œ ๋ถ„๋ฆฌํ•ด์„œ ๋”ฐ๋กœ ๊ตฌํ˜„์„ ํ•˜์—ฌ ํ•„์š”ํ•œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์•Œ์•„์„œ ๊ฐ€์ ธ๊ฐ€์„œ ์‚ฌ์šฉํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

Network ๊ตฌํ˜„

import 'package:dio/dio.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,
  }) {
    if (customOptions != null) {
      options = customOptions!;
    }
    options.baseUrl = clientBaseUrl;
    // options.connectTimeout = Duration(seconds: 5);
    // options.receiveTimeout = Duration(seconds: 3);
    interceptors.add(LogInterceptor(requestBody: true, responseBody: true));
    interceptors.addAll(customInterceptors);
  }
}
import 'package:app_frame_sample/data/network/client/base_http_client.dart';

class TodoApiClient extends BaseHttpClient {
  TodoApiClient({
    required super.clientBaseUrl,
    required super.customInterceptors,
    super.customOptions,
  });
}
import 'dart:io';

import 'package:app_frame_sample/application/application.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:package_info_plus/package_info_plus.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);

    super.onRequest(options, handler);
  }

  Future<void> _setHeaderWithAccessToken(Map<String, dynamic> headers) async {
    headers[HttpHeaders.authorizationHeader] =
        await AccessManager.getAccessToken();
  }

  Future<void> _setHeaderWithPackageInfo(Map<String, dynamic> headers) async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    headers['appName'] = packageInfo.appName;
    headers['packageName'] = packageInfo.packageName;
    headers['version'] = packageInfo.version;
    headers['buildNumber'] = 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['operatingSystem'] = 'Android';
      headers['operatingSystemVersion'] = androidInfo.version.sdkInt.toString();
      return;
    }

    if (Platform.isIOS) {
      final IosDeviceInfo iOSInfo = await deviceInfo.iosInfo;
      headers['uuid'] = iOSInfo.identifierForVendor;
      headers['operatingSystem'] = 'iOS';
      headers['operatingSystemVersion'] = iOSInfo.systemVersion;
    }
  }
}

์—ฌ๊ธฐ์„œ device ์ •๋ณด์™€ package ์ •๋ณด๋ฅผ ์ฝ๋Š”๋ฐ ์‚ฌ์šฉ๋œ ํŒจํ‚ค์ง€๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์œ„ ์ฝ”๋“œ์ƒ์— ๊ตฌํ˜„๋œ AccessManager ๋Š” data ๋ ˆ์ด์–ด๋ณด๋‹ค๋Š” application ๋ ˆ์ด์–ด์— ์†ํ•˜๋Š”๊ฒŒ ๋งž๋Š” ๊ฒƒ ๊ฐ™์•„์„œ ์ผ๋‹จ์€ application layer ์— ๊ตฌํ˜„ํ•ด๋‘์—ˆ๊ณ  ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. shared_preferences ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

import 'package:shared_preferences/shared_preferences.dart';

class AccessManager {
  static const String accessTokenKey = 'accessToken';

  static Future<void> saveAccessToken(String accessToken) async {
    final SharedPreferences preferences = await SharedPreferences.getInstance();
    await preferences.setString(accessTokenKey, accessToken);
  }

  static Future<String> getAccessToken() async {
    final SharedPreferences preferences = await SharedPreferences.getInstance();
    final String? accessToken = preferences.getString(accessTokenKey);
    if (accessToken == null) {
      return '';
    }
    
    return accessToken;
  }
}

Last updated