uploadcare_client 1.4.3

  • Readme
  • Changelog
  • Example
  • Installing
  • 69

alt flutter uploadcare client

Flutter Uploadcare Client #

Breaking changes #

The Flutter team made a breaking change with the ImageProvider in Flutter 1.10.15. Also, the Flutter team doesn't recommend use flutter_web in 1.9, that why I specify flutter SDK constraints for the 2.0.0 version. This version added the ability to upload files in flutter_web environment.

If you have the following error, please upgrade to 2.0.0

The method 'UploadcareImageProvider.load' has fewer positional arguments than those of overridden method 'ImageProvider.load'

Limitations #

  • It's impossible to use AuthSchemeRegular auth scheme in flutter_web with fetch API because Date request header is forbidden for XMLRequest https://fetch.spec.whatwg.org/#forbidden-header-name.
  • It's impossible to run the upload process in the separate isolate.

Introduction #

Uploadcare is a complete file handling platform that helps you ship products faster and focus on your business goals, not files. With Uploadcare, you can build an infrastructure, optimize content, conversions, load times, traffic, and user experience. Read more...

Implemented features: #

Roadmap: #

  • document conversion

alt flutter uploadcare example

alt flutter uploadcare face rocognition example

alt flutter uploadcare web upload video

alt flutter uploadcare web upload image

Example: #

Note: you can omit privateKey, but in this case only Upload API will be available. (CDN API also will be available).

How to use library:

// create client with simple auth scheme
final client = UploadcareClient.withSimpleAuth(
  publicKey: 'UPLOADCARE_PUBLIC_KEY',
  privateKey: 'UPLOADCARE_PRIVATE_KEY',
  apiVersion: 'v0.5',
);
// or create client with reqular auth scheme
final client = UploadcareClient.withRegularAuth(
  publicKey: 'UPLOADCARE_PUBLIC_KEY',
  privateKey: 'UPLOADCARE_PRIVATE_KEY',
  apiVersion: 'v0.5',
);
// or more flexible
final client = UploadcareClient(
  options: ClientOptions(
    authorizationScheme: AuthSchemeRegular(
      apiVersion: 'v0.5',
      publicKey: 'UPLOADCARE_PUBLIC_KEY',
      privateKey: 'UPLOADCARE_PRIVATE_KEY',
    ),
    // rest options...
  ),
);

UploadcareClient has at the moment 4 API section

final ApiUpload upload;
final ApiFiles files;
final ApiVideoEncoding videoEncoding;
final ApiGroups groups;

You can use each api section separately, for example:

final options = ClientOptions(
  authorizationScheme: AuthSchemeRegular(
    apiVersion: 'v0.5',
    publicKey: 'UPLOADCARE_PUBLIC_KEY',
    privateKey: 'UPLOADCARE_PRIVATE_KEY',
  )
);

final upload = ApiUpload(options: options);
final fileId = await upload.base(SharedFile(File('...some/file')));
// ...etc.

Using with widgets #

The library provides UploadcareImageProvider for more effective use in the widget ecosystem, how to use image provider:

Image(
  image: UploadcareImageProvider(
    'uploadcare-image-file-uuid',
    // optional, apply transformations to the image
    transformations: [
      BlurTransformation(50),
      GrayscaleTransformation(),
      InvertTransformation(),
      ImageResizeTransformation(Size.square(58))
    ],
    // rest image props...
  ),
)

Cancellation #

You can cancel the upload process by using CancelToken, each method from the upload section (auto, base, multipart) accepts cancelToken property, which you can use to cancel the upload process. This feature works only with files upload because Uploadcare isn't supporting interrupt upload by URL

...

final cancelToken = CancelToken();

...

try {
  final fileId = await client.upload.multipart(
    SharedFile(File('/some/file')),
    cancelToken: cancelToken,
  );
} on CancelUploadException catch (e) {
  // cancelled
}

...

// somewhere in code
cancelToken.cancel();

Face Recognition #

...
final files = ApiFiles(options: options);

final FacesEntity entity = await files.detectFacesWithOriginalImageSize('image-id');
...
RenderBox renderBox = context.findRenderObject();

return FractionallySizedBox(
  widthFactor: 1,
  heightFactor: 1,
  child: Stack(
    children: <Widget>[
      Positioned.fill(
        child: Image(
          image: UploadcareImageProvider(widget.imageId),
          fit: BoxFit.contain,
          alignment: Alignment.topCenter,
        ),
      ),
      ...entity
          .getRelativeFaces(
        Size(
          renderBox.size.width,
          renderBox.size.width /
              entity.originalSize.aspectRatio,
        ),
      )
          .map((face) {
        return Positioned(
          top: face.top,
          left: face.left,
          child: Container(
            width: face.size.width,
            height: face.size.height,
            decoration: BoxDecoration(
              color: Colors.black12,
              border: Border.all(color: Colors.white54, width: 1.0),
            ),
          ),
        );
      }).toList(),
    ],
  ),
);
...

Gif to video #

final file = CdnFile('gif-id-1')
  ..transform(GifToVideoTransformation([
    VideoFormatTransformation(VideoFormatTValue.Mp4),
    QualityTransformation(QualityTValue.Best),
  ]));

...

VideoPlayerController.network(file.url);

Video encoding #

...

final videoEncoding = ApiVideoEncoding(options);

final VideoEncodingConvertEntity result = await videoEncoding.process({
  'video-id-1': [
    CutTransformation(
      const const Duration(seconds: 10),
      length: const Duration(
        seconds: 30,
      ),
    )
  ],
  'video-id-2': [
    VideoResizeTransformation(const Size(512, 384)),
    VideoThumbsGenerateTransformation(10),
  ],
});

final Stream<VideoEncodingJobEntity> processingStream = videoEncoding.statusAsStream(
  result.results.first.token,
  checkInterval: const Duration(seconds: 2),
)..listen((VideoEncodingJobEntity status) {
  // do something
})

Upload in isolates #

final client = UploadcareClient(
  options: ClientOptions(
    // setup max concurrent running isolates
    maxIsolatePoolSize: 3,
    authorizationScheme: AuthSchemeSimple(
      apiVersion: 'v0.5',
      publicKey: env['UPLOADCARE_PUBLIC_KEY'],
    ),
  ),
);

final id = await client.upload.auto(
  SharedFile(File('/some/file')),
  runInIsolate: true,
);

[1.4.2] - Tue Dec 10 2019

  • Added flutter SDK version constraint
  • Described limitations in README

[2.0.0-rc.1] - Mon Dec 9 2019 [BREAKING CHANGES]

  • Changed UploadcareImageProvider.load method arguments, related to the SDK changes for ImageProvider
  • Added ability to upload in flutter_web environment
    • Added SharedFile abstraction, which works on both mobile & web
    • Changed res argument type in ApiSectionUpload.auto method
    • Changed file argument type in ApiSectionUpload.base & ApiSectionUpload.multipart methods
  • Added ability to upload files in example project in flutter_web environment

[1.4.1] - Mon Dec 9 2019

  • Updated dependencies to the latest version

[1.4.0] - Mon Nov 4 2019

  • Improved auto method from ApiUpload. Now you can pass file string to this method and client try to parse him.
  • Added ability to run upload process in separate isolate
  • Added maxIsolatePoolSize options to ClientOptions which control concurrent isolates amount

[1.3.0] - Fri Nov 1 2019

  • Added FacesEntity which holds Face Recognition data of an image related to the original size
  • Added getFacesEntity method to ApiFiles which returns FacesEntity
  • Marked detectFaces method to deprecated. Use getFacesEntity instead.
  • Added face recognition screen to the example project

[1.2.2] - Wed Oct 30 2019

  • Fixed case when content_type value for upload is null with filenames in uppercase (related to mime_type package).

[1.2.1] - Tue Oct 29 2019

  • Refactored ConcurrentRunner class

[1.2.0] - Mon Oct 28 2019

  • Added detectFaces method for ApiFiles section
  • Added OverlayTransformation applied to an image
  • Added GifToVideoTransformation applied to gif
  • Added includeRecognitionInfo parameter to ApiFiles section for file & list methods.
    • Note: this feature will be available only since v0.6 version of REST API
  • Covered all transformation with test
  • Improved documentation

[1.1.0] - Fri Oct 25 2019

  • Added ability to cancel file upload with CancelToken
  • Optimized chunked upload
  • Changed header names to constants from dart:io HttpHeaders
  • Fixed progress data with multipart upload
  • Refactored example project

[1.0.2] - Mon Oct 21 2019

  • Minor grammatical fixes

[1.0.1] - Tue Oct 15 2019

  • Made privateKey optional

[1.0.0] - Thu Sep 26 2019

  • Moved to stable version

[0.0.1] - Thu Sep 26 2019

  • Initial release

example/README.md

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:image_picker/image_picker.dart';
import 'package:uploadcare_client/uploadcare_client.dart';

void main() async {
  await DotEnv().load('.env');

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter uploadcare client example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter uploadcare client example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  UploadcareClient _uploadcareClient;

  @override
  void initState() {
    super.initState();
    _uploadcareClient = UploadcareClient.withSimpleAuth(
      publicKey: DotEnv().env['UPLOADCARE_PUBLIC_KEY'],
      privateKey: DotEnv().env['UPLOADCARE_PRIVATE_KEY'],
      apiVersion: 'v0.5',
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text('Upload'),
              onPressed: _onUpload,
            ),
            const SizedBox(
              height: 20,
            ),
            RaisedButton(
              child: Text('Files'),
              onPressed: _onFiles,
            ),
          ],
        ),
      ),
    );
  }

  Future _onUpload() async {
    final file = await showModalBottomSheet<File>(
        context: context,
        builder: (context) {
          return Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ListTile(
                title: Text('Pick image from gallery'),
                onTap: () async => Navigator.pop(
                  context,
                  await ImagePicker.pickImage(
                    source: ImageSource.gallery,
                  ),
                ),
              ),
              ListTile(
                title: Text('Pick image from camera'),
                onTap: () async => Navigator.pop(
                  context,
                  await ImagePicker.pickImage(
                    source: ImageSource.camera,
                  ),
                ),
              ),
              ListTile(
                title: Text('Pick video from gallery'),
                onTap: () async => Navigator.pop(
                  context,
                  await ImagePicker.pickVideo(
                    source: ImageSource.gallery,
                  ),
                ),
              ),
              ListTile(
                title: Text('Pick video from camera'),
                onTap: () async => Navigator.pop(
                  context,
                  await ImagePicker.pickVideo(
                    source: ImageSource.camera,
                  ),
                ),
              ),
            ],
          );
        });

    if (file != null) {
      Navigator.push(
          context,
          MaterialPageRoute(
            fullscreenDialog: true,
            builder: (context) => UploadScreen(
              file: file,
              uploadcareClient: _uploadcareClient,
            ),
          ));
    }
  }

  void _onFiles() => Navigator.push(
      context,
      MaterialPageRoute(
        fullscreenDialog: true,
        builder: (context) => FilesScreen(
          uploadcareClient: _uploadcareClient,
        ),
      ));
}

class UploadScreen extends StatefulWidget {
  UploadScreen({
    Key key,
    this.file,
    this.uploadcareClient,
  }) : super(key: key);

  final File file;
  final UploadcareClient uploadcareClient;

  _UploadScreenState createState() => _UploadScreenState();
}

enum UploadState {
  Uploading,
  Error,
  Uploaded,
  Canceled,
}

class _UploadScreenState extends State<UploadScreen> {
  CancelToken _cancelToken;
  StreamController<ProgressEntity> _progressController;
  UploadState _uploadState;
  String _fileId;
  FileInfoEntity _fileInfoEntity;
  String _cancelMessage;

  @override
  void initState() {
    super.initState();

    _uploadState = UploadState.Uploading;
    _cancelToken = CancelToken('canceled by user');
    _progressController = StreamController();

    WidgetsBinding.instance.addPostFrameCallback((_) async {
      try {
        _fileId = await widget.uploadcareClient.upload.auto(
          widget.file,
          cancelToken: _cancelToken,
          storeMode: false,
          onProgress: (progress) => _progressController.add(progress),
        );
        _fileInfoEntity = await widget.uploadcareClient.files.file(_fileId);
        _uploadState = UploadState.Uploaded;
      } on CancelUploadException catch (e) {
        _uploadState = UploadState.Canceled;
        _cancelMessage = e.message;
      } catch (e) {
        _uploadState = UploadState.Error;
      }

      setState(() {});
    });
  }

  @override
  void dispose() {
    _progressController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Upload screen'),
      ),
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Column(
                    children: <Widget>[
                      if (_uploadState == UploadState.Uploading)
                        StreamBuilder(
                          stream: _progressController.stream,
                          builder: (context,
                              AsyncSnapshot<ProgressEntity> snapshot) {
                            return Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: <Widget>[
                                LinearProgressIndicator(
                                  value: snapshot.hasData
                                      ? snapshot.data.value
                                      : null,
                                ),
                                const SizedBox(
                                  height: 10,
                                ),
                                if (snapshot.hasData)
                                  Row(
                                    mainAxisAlignment: MainAxisAlignment.center,
                                    children: <Widget>[
                                      Text(
                                          'uploaded: ${snapshot.data.uploaded}'),
                                      const SizedBox(
                                        width: 10,
                                      ),
                                      Text('total: ${snapshot.data.total}'),
                                    ],
                                  ),
                              ],
                            );
                          },
                        ),
                      if (_uploadState == UploadState.Uploaded &&
                          _fileInfoEntity.isImage)
                        Image(
                          image: UploadcareImageProvider(_fileId),
                          fit: BoxFit.contain,
                        ),
                      if (_uploadState == UploadState.Canceled)
                        Text(
                          _cancelMessage,
                          style: TextStyle(color: Colors.red),
                        ),
                      const SizedBox(
                        height: 20,
                      ),
                      Text(widget.file.path),
                    ],
                  ),
                ),
              ),
              const SizedBox(
                height: 20,
              ),
              RaisedButton(
                child: Text('Cancel'),
                onPressed: _uploadState == UploadState.Uploading &&
                        !_cancelToken.isCanceled
                    ? _cancelToken.cancel
                    : null,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class FilesScreen extends StatefulWidget {
  FilesScreen({
    Key key,
    this.uploadcareClient,
  }) : super(key: key);

  final UploadcareClient uploadcareClient;

  _FilesScreenState createState() => _FilesScreenState();
}

class _FilesScreenState extends State<FilesScreen> {
  StreamController<List<FileInfoEntity>> _filesController;
  int _total;
  int _limit;

  @override
  void initState() {
    super.initState();

    _filesController = StreamController();
    _limit = 200;

    WidgetsBinding.instance.addPostFrameCallback((_) async {
      final files = await widget.uploadcareClient.files.list(
        offset: 0,
        limit: _limit,
        stored: null,
      );

      setState(() {
        _total = files.total;
      });

      _filesController.add(files.results);
    });
  }

  @override
  void dispose() {
    _filesController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Files screen'),
        actions: <Widget>[
          if (_total != null)
            Padding(
              padding: const EdgeInsets.only(right: 15.0),
              child: Center(child: Text('Total: $_total')),
            ),
        ],
      ),
      body: StreamBuilder(
        stream: _filesController.stream,
        builder: (BuildContext context,
            AsyncSnapshot<List<FileInfoEntity>> snapshot) {
          if (!snapshot.hasData)
            return Center(
              child: CircularProgressIndicator(),
            );

          final files = snapshot.data;

          return ListView.builder(
            itemCount: files.length,
            itemBuilder: (context, index) {
              final file = files[index];

              return ListTile(
                title: Text('Filename: ${file.filename}'),
                subtitle: Text('Type: ${file.isImage ? 'image' : 'video'}'),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    if (file.isImage)
                      IconButton(
                        icon: Icon(Icons.face),
                        onPressed: () => Navigator.push(
                          context,
                          MaterialPageRoute(
                            fullscreenDialog: true,
                            builder: (context) => FaceDetectScreen(
                              uploadcareClient: widget.uploadcareClient,
                              imageId: file.id,
                            ),
                          ),
                        ),
                      ),
                    IconButton(
                      icon: Icon(
                        Icons.delete,
                        color: Colors.redAccent,
                      ),
                      onPressed: () async {
                        await widget.uploadcareClient.files.remove([file.id]);
                        setState(() {
                          _total--;
                        });
                        _filesController.add(files..removeAt(index));
                      },
                    ),
                  ],
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class FaceDetectScreen extends StatefulWidget {
  FaceDetectScreen({
    Key key,
    this.uploadcareClient,
    this.imageId,
  }) : super(key: key);

  final UploadcareClient uploadcareClient;
  final String imageId;

  @override
  _FaceDetectScreenState createState() => _FaceDetectScreenState();
}

class _FaceDetectScreenState extends State<FaceDetectScreen> {
  Future<FacesEntity> _future;
  GlobalKey _key;

  @override
  void initState() {
    super.initState();

    _key = GlobalKey();
    _future = widget.uploadcareClient.files.getFacesEntity(widget.imageId);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Face detection screen'),
      ),
      body: FutureBuilder(
        future: _future,
        builder: (BuildContext context, AsyncSnapshot<FacesEntity> snapshot) {
          if (!snapshot.hasData)
            return Center(
              child: CircularProgressIndicator(),
            );

          if (!snapshot.data.hasFaces)
            return Center(
              child: Text('No faces has been detected'),
            );

          RenderBox renderBox = context.findRenderObject();

          return FractionallySizedBox(
            widthFactor: 1,
            heightFactor: 1,
            child: Stack(
              children: <Widget>[
                Positioned.fill(
                  child: Image(
                    key: _key,
                    image: UploadcareImageProvider(widget.imageId),
                    fit: BoxFit.contain,
                    alignment: Alignment.topCenter,
                  ),
                ),
                ...snapshot.data
                    .getRelativeFaces(
                  Size(
                    renderBox.size.width,
                    renderBox.size.width /
                        snapshot.data.originalSize.aspectRatio,
                  ),
                )
                    .map((face) {
                  return Positioned(
                    top: face.top,
                    left: face.left,
                    child: Container(
                      width: face.size.width,
                      height: face.size.height,
                      decoration: BoxDecoration(
                        color: Colors.black12,
                        border: Border.all(color: Colors.white54, width: 1.0),
                      ),
                    ),
                  );
                }).toList(),
              ],
            ),
          );
        },
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  uploadcare_client: ^1.4.3

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:uploadcare_client/uploadcare_client.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
63
Health:
Code health derived from static analysis. [more]
56
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
69
Learn more about scoring.

We analyzed this package on Dec 10, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.13.1+4
  • Flutter: 1.12.13+hotfix.2

Health issues and suggestions

Fix lib/src/image_provider.dart. (-43.75 points)

Analysis of lib/src/image_provider.dart failed with 2 errors:

line 46 col 24: 'UploadcareImageProvider.load' ('ImageStreamCompleter Function(NetworkImage)') isn't a valid override of 'ImageProvider.load' ('ImageStreamCompleter Function(NetworkImage, Future

line 46 col 64: 2 positional argument(s) expected, but 1 found.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.2.2 <3.0.0
crypto ^2.1.3 2.1.4
equatable ^1.0.1 1.0.1
flutter 0.0.0
http ^0.12.0+2 0.12.0+2
http_parser ^3.1.3 3.1.3
meta ^1.1.7 1.1.8
mime_type ^0.2.4 0.2.4
Transitive dependencies
async 2.4.0
charcode 1.1.2
collection 1.14.11 1.14.12
convert 2.1.1
path 1.6.4
pedantic 1.9.0
sky_engine 0.0.99
source_span 1.5.5
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
dotenv ^1.0.0
flutter_test
mockito ^4.1.1
test ^1.6.3