Arkaan Sayed
Arkaan Sayed

Reputation: 51

Best Practice for uploading image files in flutter

I am quite new to the image upload scenario, can the community suggest best practices to follow while uploading an image file to the server. The end point is already developed and right now I am making a direct call to the api using dio along with retrofit. I wanted to know about the best practices when it comes based on industry standards, which includes error handling and handling any other scenario, like re-uploading etc..

Upvotes: 0

Views: 155

Answers (1)

AngDrew
AngDrew

Reputation: 206

if you are using dio, you might familiar with "interceptor" right? there's plenty way to make your interceptor clean. and here's mine

class AppInterceptors extends Interceptor {
  const AppInterceptors(this.dio);
  final Dio dio;

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    String? token = await TokenStorage.getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }

    super.onRequest(options, handler);
  }

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    ErrorResponse resp = ErrorResponse.fromJson(err.response?.data);
    log(
      '[${err.response?.statusCode ?? '?'}] ${err.message}',
      name: 'interceptor',
    );
    log('Error ID: ${resp.error?.errorId}', name: 'Interceptor');
    log('Error Messages:', name: 'Interceptor');
    for (String msg in resp.error?.errorMessages ?? <String>[]) {
      log('  - $msg', name: 'Interceptor');
    }

    bool unauthorized = err.response?.statusCode == 401;
    String? refreshToken = await TokenStorage.getRefreshToken();
    log('${err.response?.statusCode} and $unauthorized', name: 'findme');

    if (unauthorized && refreshToken.notEmptyOrNull) {
      AuthApi refresher = AuthApi.create(dio);
      LoginResponse? refreshResult = await refresher
          .refreshToken(
        RefreshTokenRequest(refreshToken: refreshToken).toJson(),
      )
          .onError<DioException>((DioException? error, StackTrace stackTrace) {
        Future<void>.microtask(() async {
          log('expiredRefreshToken: $refreshToken');

          await TokenStorage.deleteAllToken();
          ToastHelper.error('Session expired, please login again.');

          throwToAuthScreen();
        });

        return null;
      });

      if (refreshResult != null) {
        await TokenStorage.writeToken(refreshResult.jwtToken ?? '');
        await TokenStorage.writeRefreshToken(refreshResult.refreshToken ?? '');

        // retry the previous request
        RequestOptions options = err.requestOptions;
        options.headers['Authorization'] = 'Bearer ${refreshResult.jwtToken}';
        try {
          Response<dynamic> response = await dio.fetch(options);
          return handler.resolve(response);
        } catch (e) {
          ToastHelper.error(
            'An unknown error occurred, please try again later.',
          );
          return;
        }
      }
    } else {
      throwToAuthScreen();
      ToastHelper.error('There was a problem, Please try again.');
    }

    return handler.reject(err);
  }
}

and here's my http_client

class HttpClient {
  static Dio init({
    required String baseUrl,
    int sendTimeout = 60000,
    int connectTimeout = 60000,
    int receiveTimeout = 60000,
    String contentType = Headers.jsonContentType,
    ResponseType responseType = ResponseType.plain,
  }) {
    Dio dio = Dio(BaseOptions(
      baseUrl: baseUrl,
      receiveTimeout: Duration(seconds: receiveTimeout),
      connectTimeout: Duration(seconds: connectTimeout),
      sendTimeout: Duration(seconds: sendTimeout),
      contentType: contentType,
      responseType: responseType,
    ));

    // TODO(Andrew): add ssl pinning
    // dio.interceptors.add(
    //   CertificatePinningInterceptor(),
    // );

    dio.interceptors.add(AppInterceptors(dio));
    dio.interceptors.add(PrettyDioLogger(
      request: kDebugMode,
      requestBody: kDebugMode,
      requestHeader: kDebugMode,
      responseHeader: false,
      responseBody: kDebugMode,
      error: true,
      logPrint: (Object object) => log(
        object.toString(),
        name: DateTime.now().toTimeShort,
      ),
      maxWidth: 80,
    ));
    dio.interceptors.add(CurlLoggerDioInterceptor(printOnSuccess: true));
    dio.interceptors.add(RetryInterceptor(
      dio: dio,
      retries: 3,
      retryDelays: <Duration>[3.seconds, 5.seconds, 15.seconds],
    ));

    return dio;
  }
}

RetryInterceptor is package and extension for dio interceptor. you can make your own interceptor just like what i wrote above (AppInterceptor) this interceptor has 3 function: onRequest, onError, onResponse. these 3 functions are self explanatory just as their name suggested. you can have many interceptor which enable you to manage every request of your app like adding token to header, there's plenty way to do it but in my code, i made it as modular as possible. combined with riverpod i can make multiple endpoint using riverpod and use it modularly

@riverpod
class DioClient extends _$DioClient {
  @override
  Dio build() {
    return HttpClient.init(baseUrl: F.baseUrl);
  }
}

Upvotes: 0

Related Questions