Importing VideoCrop library natively with current Flutter project

Sardor Islomov
Flutter Community
Published in
5 min readOct 7, 2020
Flutter Logo

In flutter there are no any working libraries which help you to CROP. Yes CROP video files.You can find trim, crop view plugins for flutter but you can’t find any plugins for flutter which helps to crop existing video file and save it in storage.

Question:How we gonna crop video files in Flutter?

Answer:Implement for each platform its own cropping, to be more exact use existing cropping libraries for Android and IOS platforms separately and connect them with Flutter using “channels”.

It is easy, isn’t it? But challenge comes when implementing cropping library in Android.First you need to find proper library, there 10s of them in github but most of them does not work well or at least works with some bugs.One of the best video cropping libraries out there is VideoCrop.

Talk less, write code.

In Summary, we will implement below steps:

  • Opening Flutter project with Android module view mode
  • Import VideoCrop library as a submodule not via maven or gradle
  • Implement MainActivity.kt file in Android module for Flutter
  • Create channel in Flutter and send video file to Native Android

Opening Flutter project with Android module view mode

Once you opened project make sure you migrated your codebase into AndroidX.If you dont later one you could have some compile errors in old support libraries.

Import VideoCrop library as a submodule not via maven or gradle

Tap right side of mouse and open in Android module. After that go github link of VideoCrop and download it. Import only VideoCrop folder into your project as a submodule.

After importing videoCrop submodule your Android view of project should look like this.

After implementing migrate VideoCrop module into AndroidX and sync project.

And don’t forget to add VideoCrop module in app module gradle file as implementation

Implement MainActivity.kt file in Android module for Flutter

Add method Channel in configureFlutterEngine() method as below:

private val CHANNEL = "appName.MainActivity"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->

}
}
}

Call videoCrop activity as startActivityForResult

private val CHANNEL = "appName.MainActivity"
var ourResult:MethodChannel.Result?=null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if(call.method.equals("videoCrop")) {
this.ourResult = result
videoCropActivity(call.argument<String>("input")!!,call.argument<String>("output")!!)
}
}
}
}
fun videoCropActivity(input:String,outPut:String){
startActivityForResult(VideoCropActivity.createIntent(this, input, outPut), 1111);
}

Note: ourResult:MethodChannel.Result is object from Dart which helps us to return result from Native to Flutter.

Lastly, implement onActivityResult() method of FlutterActivity.kt

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 1111 && resultCode == FlutterActivity.RESULT_OK){
if(ourResult!=null){
ourResult?.success(-1)
Toast.makeText(this,"Success",Toast.LENGTH_LONG).show()
}else
Toast.makeText(this,"Success result null",Toast.LENGTH_LONG).show()
}
}

Overall our MainActivity will look like this.

class MainActivity: FlutterActivity() {

private val CHANNEL = "appName.MainActivity"
var ourResult:MethodChannel.Result?=null

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if(call.method.equals("videoCrop")) {
this.ourResult = result
videoCropActivity(call.argument<String>("input")!!,call.argument<String>("output")!!)
}
}
}

fun videoCropActivity(input:String,outPut:String){
startActivityForResult(VideoCropActivity.createIntent(this, input, outPut), 1111);
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 1111 && resultCode == FlutterActivity.RESULT_OK){
if(ourResult!=null){
ourResult?.success(-1)
Toast.makeText(this,"Success",Toast.LENGTH_LONG).show()
}else
Toast.makeText(this,"Success result null",Toast.LENGTH_LONG).show()
}
}


}

Create channel in Flutter and send video file to Native Android

Create object called androidPlatform using MethodChannel from Flutter.

static const androidPlatform = const MethodChannel("appName.MainActivity");

this object is responsible for exchanging date between flutter and android.To make picture clear lets create some widget called MainWidget and whole structure will look like this.

class _MainWidgetState extends State<MainWidget>{
static const androidPlatform = const MethodChannel(Constants.ANDROID_CHANNEL);

@override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
onPressed: onCrop,
child: Text("Crop"),
),
);
}

void onCrop(){

}

}

Let’s implement onCrop method together.First retrieve video from camera or gallery. I would suggest to use ImagePicker. It is easy to pick.

void onCrop(){
final pickedFile = await mediaPicker.getVideo(source: ImageSource.camera);

}

After retrieving video let’s get output path which our cropped video will be saved.

Future<String> _outputVideoPath() async {
final directory = await getExternalStorageDirectory();
File newFile = new File("${directory.path}/converted_video.mp4");
newFile.createSync(recursive: true);
return newFile.path;
}

Note: getExternalStorageDirectory() works only for Android.

void onCrop(){
final pickedFile = await mediaPicker.getVideo(source: ImageSource.camera);
final String outputPath = await _outputVideoPath();
print("output file:${outputPath}");
File outPutFile = new File(outputPath);
}

After getting output file path we can start cropping.

void onCrop(){
final pickedFile = await mediaPicker.getVideo(source: ImageSource.camera);

final String outputPath = await _outputVideoPath();
print("output file:${outputPath}");
File outPutFile = new File(outputPath);
final int result = await androidPlatform.invokeMethod('videoCrop',
{'input': "${pickedFile.path}", 'output': "$outputPath"});
if (result == -1) {
print("Success");
}
}

Finally our MainWidget will look like this.

class _MainWidgetState extends State<MainWidget>{
static const androidPlatform = const MethodChannel(Constants.ANDROID_CHANNEL);

@override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
onPressed: onCrop,
child: Text("Crop"),
),
);
}

void onCrop(){
final pickedFile = await mediaPicker.getVideo(source: ImageSource.camera);

final String outputPath = await _outputVideoPath();
print("output file:${outputPath}");
File outPutFile = new File(outputPath);
final int result = await androidPlatform.invokeMethod('videoCrop',
{'input': "${pickedFile.path}", 'output': "$outputPath"});
if (result == -1) {
print("Success");
}
}

}

That’s it. In the next posts I will be posting about How to implement VideoCrop for IOS natively.

https://www.twitter.com/FlutterComm

--

--