Search This Blog

Thursday, August 9, 2018

Flutter - Capture Image from camera or gallery and apply crop.

In modern application, if you implementing user profile feature. So, you should implement user profile pic and allow a user to set and change profile pic. A user can set a profile pic from his/her mobile gallery or capture image from camera. 
Image picker in flutter
Besides just picking images from gallery and camera. You can crop it with desired aspect ratio. It is very important if you noticed every app like Facebook, Google etc asks you to crop the image into a square because it fits the best for different use cases.

In this post, we’ll develop an application that picks an image from camera or gallery. After that, we crop it for fit in a circular image widget.

In this project user will have following choices:
  • Capture profile picture from the camera.
  • Choose profile picture from gallery.
  • Cancel.
The user will need to choose one option from the above three options and then depending on the option chosen by the user. We will either capture an image from the camera or open the gallery.



We have created a Flutter plugin for this post. You can use it instantly. version:1.1.2

flutter-image-picker-plugin

 
let's start its implementation with the following steps.

Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. For pick image from gallery and camera, we have used image_picker: "^0.4.5" and for crop selected we have used image_cropper: ^0.0.4. As you can see, we have declared it in pubspec.yaml file.

Image crop and picker dart lib

3. Open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
main.dart
import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/home_screen.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Image picker', theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF167F67), ), home: new HomeScreen(title: 'Flutter Image picker'), ); } }
4. After that create HomeScreen(home_screen.dartwidget. It is root widget of our project. Here, we have implemented two interfaces TickerProviderStateMixin and ImagePickerListener.  We using TickerProviderStateMixin for handle images from local storage and ImagePickerListener provide a callback method from image picker handler class. We have created an instance of animation controller. It will control bottom to up slide animation.
home_screen.dart
@override void initState() { super.initState(); _controller = new AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); imagePicker=new ImagePickerHandler(this,_controller); imagePicker.init(); }


5. Now modify build method of home_screen.dart. Here, we have used stack widget for display empty image view.

Empty image picker box
home_screen.dart
new Stack( children: <Widget>[ new Center( child: new CircleAvatar( radius: 80.0, backgroundColor: const Color(0xFF778899), ), ), new Center( child: new Image.asset("assets/photo_camera.png"), ), ], ) }
for display selected and cropped image, we have used another widget group.
Selected image picker box
home_screen.dart
new Container( height: 160.0, width: 160.0, decoration: new BoxDecoration( color: const Color(0xff7c94b6), image: new DecorationImage( image: new ExactAssetImage(_image.path), fit: BoxFit.cover, ), border: Border.all(color: Colors.red, width: 5.0), borderRadius: new BorderRadius.all(const Radius.circular(80.0)), ), ),
after that merge all code snippet and you will see final home_screen.dart look like below.
home_screen.dart
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_handler.dart'; import 'package:flutter_image_ppicker/image_picker_dialog.dart'; class HomeScreen extends StatefulWidget { HomeScreen({Key key, this.title}) : super(key: key); final String title; @override _HomeScreenState createState() => new _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin,ImagePickerListener{ File _image; AnimationController _controller; ImagePickerHandler imagePicker; @override void initState() { super.initState(); _controller = new AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); imagePicker=new ImagePickerHandler(this,_controller); imagePicker.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title, style: new TextStyle( color: Colors.white ), ), ), body: new GestureDetector( onTap: () => imagePicker.showDialog(context), child: new Center( child: _image == null ? new Stack( children: <Widget>[ new Center( child: new CircleAvatar( radius: 80.0, backgroundColor: const Color(0xFF778899), ), ), new Center( child: new Image.asset("assets/photo_camera.png"), ), ], ) : new Container( height: 160.0, width: 160.0, decoration: new BoxDecoration( color: const Color(0xff7c94b6), image: new DecorationImage( image: new ExactAssetImage(_image.path), fit: BoxFit.cover, ), border: Border.all(color: Colors.red, width: 5.0), borderRadius: new BorderRadius.all(const Radius.circular(80.0)), ), ), ), ), ); } @override userImage(File _image) { setState(() { this._image = _image; }); } }


6. Now create image_picker_handler.dart. It will handle user interface of image picker widget or dialog. Here, we have created openCarmra and openGallery methods to get an image. In this file, we have created cropImage method.  As you can see.


Image crop
image_picker_handler.dart
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_dialog.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; class ImagePickerHandler { ImagePickerDialog imagePicker; AnimationController _controller; ImagePickerListener _listener; ImagePickerHandler(this._listener, this._controller); openCamera() async { imagePicker.dismissDialog(); var image = await ImagePicker.pickImage(source: ImageSource.camera); cropImage(image); } openGallery() async { imagePicker.dismissDialog(); var image = await ImagePicker.pickImage(source: ImageSource.gallery); cropImage(image); } void init() { imagePicker = new ImagePickerDialog(this, _controller); imagePicker.initState(); } Future cropImage(File image) async { File croppedFile = await ImageCropper.cropImage( sourcePath: image.path, ratioX: 1.0, ratioY: 1.0, maxWidth: 512, maxHeight: 512, ); _listener.userImage(croppedFile); } showDialog(BuildContext context) { imagePicker.getImage(context); } } abstract class ImagePickerListener { userImage(File _image); }
if you want to use this class. Then you have to call init method to create an instance of image picker dialog. After that call showDialog method for show image picker. 

7. At the end, create a final class of project that is image_picker_dialog.dart. Here, we have defined getImage method that we calling from image_picker_halder.dart. It will show a dialog with a bottom to up slide animation. As you can see above video.
image_picker_dialog.dart
getImage(BuildContext context) { if (_controller == null || _drawerDetailsPosition == null || _drawerContentsOpacity == null) { return; } _controller.forward(); showDialog( context: context, builder: (BuildContext context) => new SlideTransition( position: _drawerDetailsPosition, child: new FadeTransition( opacity: new ReverseAnimation(_drawerContentsOpacity), child: this, ), ), ); }


 
in this file, we have created a reusable method roundedButton. By using this method, we have created the following image picker and a cancel button on the dialog.


Image picker options

image_picker_dialog.dart
Widget roundedButton( String buttonLabel, EdgeInsets margin, Color bgColor, Color textColor) { var loginBtn = new Container( margin: margin, padding: EdgeInsets.all(15.0), alignment: FractionalOffset.center, decoration: new BoxDecoration( color: bgColor, borderRadius: new BorderRadius.all(const Radius.circular(100.0)), boxShadow: <BoxShadow>[ BoxShadow( color: const Color(0xFF696969), offset: Offset(1.0, 6.0), blurRadius: 0.001, ), ], ), child: Text( buttonLabel, style: new TextStyle( color: textColor, fontSize: 20.0, fontWeight: FontWeight.bold), ), ); return loginBtn; }
as you can see a final class of image_picker_dialog.dart. We have called above method in the build method. Here, we have final class.
image_picker_dialog.dart
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_image_ppicker/image_picker_handler.dart'; class ImagePickerDialog extends StatelessWidget { ImagePickerHandler _listener; AnimationController _controller; BuildContext context; ImagePickerDialog(this._listener, this._controller); Animation<double> _drawerContentsOpacity; Animation<Offset> _drawerDetailsPosition; void initState() { _drawerContentsOpacity = new CurvedAnimation( parent: new ReverseAnimation(_controller), curve: Curves.fastOutSlowIn, ); _drawerDetailsPosition = new Tween<Offset>( begin: const Offset(0.0, 1.0), end: Offset.zero, ).animate(new CurvedAnimation( parent: _controller, curve: Curves.fastOutSlowIn, )); } getImage(BuildContext context) { if (_controller == null || _drawerDetailsPosition == null || _drawerContentsOpacity == null) { return; } _controller.forward(); showDialog( context: context, builder: (BuildContext context) => new SlideTransition( position: _drawerDetailsPosition, child: new FadeTransition( opacity: new ReverseAnimation(_drawerContentsOpacity), child: this, ), ), ); } void dispose() { _controller.dispose(); } startTime() async { var _duration = new Duration(milliseconds: 200); return new Timer(_duration, navigationPage); } void navigationPage() { Navigator.pop(context); } dismissDialog() { _controller.reverse(); startTime(); } @override Widget build(BuildContext context) { this.context = context; return new Material( type: MaterialType.transparency, child: new Opacity( opacity: 1.0, child: new Container( padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 20.0), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new GestureDetector( onTap: () => _listener.openCamera(), child: roundedButton( "Camera", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), new GestureDetector( onTap: () => _listener.openGallery(), child: roundedButton( "Gallery", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), const SizedBox(height: 15.0), new GestureDetector( onTap: () => dismissDialog(), child: new Padding( padding: EdgeInsets.fromLTRB(30.0, 0.0, 30.0, 0.0), child: roundedButton( "Cancel", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), const Color(0xFF167F67), const Color(0xFFFFFFFF)), ), ), ], ), ), )); } Widget roundedButton( String buttonLabel, EdgeInsets margin, Color bgColor, Color textColor) { var loginBtn = new Container( margin: margin, padding: EdgeInsets.all(15.0), alignment: FractionalOffset.center, decoration: new BoxDecoration( color: bgColor, borderRadius: new BorderRadius.all(const Radius.circular(100.0)), boxShadow: <BoxShadow>[ BoxShadow( color: const Color(0xFF696969), offset: Offset(1.0, 6.0), blurRadius: 0.001, ), ], ), child: Text( buttonLabel, style: new TextStyle( color: textColor, fontSize: 20.0, fontWeight: FontWeight.bold), ), ); return loginBtn; } }

If you have followed the article carefully, you can see the app running very smoothly as shown in the video demo. But if you facing any problem.


Source Code               Flutter firebse storage apk

You can get the working project from Github and please feel free to ask in the comment section below. 


Share:

24 comments:

  1. Hi !!! thank you very much for the code !!! It is just what i needed, but i am having a little issue sometimes and came here for help. Sometimes the photo i get after the crop is just a solid grayish color and appears to happen randomly, the app uses several calls to the code (one user can take many photos, crop them and show them in separate widgets). Could you help me please ??? Thank you very much.

    ReplyDelete
    Replies
    1. Hi, Thanks for your feedback.
      I checked issue that you have reported. But it's not found. It's working fine.

      Can you show us any screenshot for the same. So, we can track and fix it.

      Delete
  2. Hi, Congratulations , nice work, nice tuto!!


    I've been followed your instructions and wala, this is the results!!


    https://www.marialijideveloper.com/?p=920&preview=true




    Ps. Although the article is in spanish, you can deduct the topic cause the images!




    Best wishes,

    ReplyDelete
    Replies
    1. Hi, Thanks for your feedback.

      I seen your widget. It look nice!

      Delete
  3. Hi, thank you for this awsome tutorial and hope you are doing well, i want to ask you if is it possible to show a snackbar just after the image is successfuly cropped ? thank you.

    ReplyDelete
    Replies
    1. Yes, you can handle it inside the "userImage" method. As you can see below, i have updated it for you

      @override
      userImage(File _image) {
      setState(() {
      this._image = _image;

      final snackBar = SnackBar(
      content: Text('Image croped!'),
      action: SnackBarAction(
      label: 'Undo',
      onPressed: () {
      // Some code to undo the change!
      },
      ),

      });
      }

      Delete
  4. Hi, Thank again for awesome tutorial, so how can I do to save the image on device , for instance 'my_imagen.jpg' or 'my_image.png' I need to retrive it later.


    Thanks so much

    ReplyDelete
  5. hey this is awesome work . but i am having an issue with camera. when i clicked on camera the camera launces and its coming in blank . is there anything i need to change or add ? thanks again.

    ReplyDelete
    Replies
    1. com.yalantis.ucrop.UCropActivity

      check it here https://pub.dartlang.org/packages/image_cropper

      Delete
    2. I dont get it . why should i use image cropper for my camera to work . i am using IOS .thanks again.

      Delete
  6. Buenos días le saludo de Ecuador tengo un problema el rato que presiono el botón aceptar dentro de galería o la cámara sale el siguiente error
    "Lo siento Flutter se a detenido" me pueden ayudar porfavor

    ReplyDelete
  7. thanks for the tutorial it was so helpful

    ReplyDelete
  8. but i got this error could you help me please
    I/Choreographer(27585): Skipped 151 frames! The application may be doing too much work on its main thread.
    V/ViewRootImpl(27585): Contents drawing finished : com.example.algeria/com.example.algeria.MainActivity
    I/Timeline(27585): Timeline: Activity_idle id: android.os.BinderProxy@ca09b52 time:220603856
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_DOWN
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_UP
    I/AudioManagerEx(27585): AudioManagerEx created
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_DOWN
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_UP
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_DOWN
    I/ViewRootImpl(27585): ViewRoot's Touch Event : ACTION_UP
    I/flutter (27585): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    I/flutter (27585): The following assertion was thrown building HomeScreen(dirty, state: _HomeScreenState#89942(tickers:
    I/flutter (27585): tracking 1 ticker)):
    I/flutter (27585): 'package:flutter/src/widgets/text.dart': Failed assertion: line 237 pos 15: 'data

    ReplyDelete
    Replies
    1. i solve this but i've got another problem when i choose a picture or take a picture with phone's camera when i did ok the app stoped(dialogueShow appear'unfortunately app_name has stopped ') please help

      Delete
    2. I am also having the same problem after using the latest plugin

      Delete
  9. how i can save image to internal storage after crop image?

    ReplyDelete
    Replies
    1. You can get file location from File instance that available in the following method of above

      @override
      userImage(File _image) {
      setState(() {
      this._image = _image;
      }

      Delete
    2. i found error


      I/BitmapCropTask(12861): Should crop: true
      W/ExifInterface(12861): Skip the tag entry since tag number is not defined: 39321
      W/ExifInterface(12861): Skip the tag entry since tag number is not defined: 34965
      W/ExifInterface(12861): Skip the tag entry since tag number is not defined: 2
      W/ExifInterface(12861): Stop reading file since a wrong offset may cause an infinite loop: 0
      I/chatty (12861): uid=10189(com.example.koala.koala) AsyncTask #7 identical 2 lines
      W/ExifInterface(12861): Stop reading file since a wrong offset may cause an infinite loop: 0

      Delete
  10. hi thank you for that work but i have problem
    D8: Program type already present: android.support.v4.os.ResultReceiver$1

    ReplyDelete
  11. Failure delivering result ResultInfo{who=null, request=2342, result=-1, data=Intent { dat=content://com.android.providers.media.documents/document/image:22948 flg=0x1 }} to activity {com.mjm.mjmgold/com.mjm.mjmgold.MainActivity}: java.lang.IllegalStateException: Received image from picker that was not requested in oreo8.0 mobile

    ReplyDelete
  12. Image picker not working in android Oreo 8.0.but other devices work perfectly including pie also.any one pls resolve the issue

    ReplyDelete
  13. How to add overlay above camera like rectangle box using flutter .Kindly help me

    ReplyDelete

WE'RE SOCIAL

Promotional

  • Cover art Blend

    Blend app is very advanced app there are lots of features like shayari, status, chat, feed....

Categories