This post is a clone of the FlutterFlow – Custom Signature Widget with Custom Code (+ convert to Base64 format) guide because the original source is no longer accessible.
Original Guide: https://docs.flutterflow.io/guides/building-custom-signature-widget-with-custom-code
FlutterFlow offers a variety of built-in widgets, but what if you need a widget with functionalities beyond the standard set? That’s where FlutterFlow’s custom code features come into play. Take the Signature widget as an example: it’s useful, but maybe you want to add some extra bells and whistles. This guide walks you through building your own enhanced Signature widget from scratch.
What we’ll cover:
Go to the Custom Code tab, create a new Custom Widget, and call it SignatureWidget
Arguments: No additional arguments.
Pubspec Dependencies: Since FlutterFlow already uses signature package from pub.dev, we can use the same package to implement our signature functionality. This also means, the import import 'package:signature/signature.dart';
should already be available.
Body: For the code body, we actually took the example code from the package’s example directory. You can directly check this file from Github. We removed some unnecessary code and this is the final code ⬇️
// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'dart:convert';
import 'package:signature/signature.dart';
import '/custom_code/actions/init_signature_controller.dart';
class SignatureWidget extends StatefulWidget {
const SignatureWidget({
Key? key,
this.width,
this.height,
}) : super(key: key);
final double? width;
final double? height;
@override
_SignatureWidgetState createState() => _SignatureWidgetState();
}
class _SignatureWidgetState extends State<SignatureWidget> {
late SignatureController _signatureController;
@override
void initState() {
super.initState();
_signatureController = // TODO
_signatureController.addListener(() => print('Value changed'));
}
@override
void dispose() {
_signatureController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ClipRect(
child: Signature(
controller: _signatureController,
backgroundColor: FlutterFlowTheme.of(context).secondaryBackground,
height: 120,
),
);
}
}
At line: 30, we are declaring a SignatureController
that will be used in the Signature Widget at line 49. This controller is crucial: it governs the Signature component, enabling functionalities like clearing the canvas or undoing previous strokes.
At line 35, you’ll see a ‘TODO’ comment. This is where you’ll want to create the SignatureController
object. Typically, the code for doing so would look something like this:
signatureController = SignatureController(
penStrokeWidth: 5,
penColor: Colors.red,
exportPenColor: Colors.red,
exportBackgroundColor: Colors.white,
);
The SignatureController
should be declared only once, as it sets the properties for our Signature widget. If we place it inside this Custom Action for the SignatureWidget
, we won’t be able to access it from other parts of the application.
So let’s fix this!
Let’s create a new action called initSignatureController
and initialize our SignatureController here. We will be creating a Singleton* class here which means that only one instance of the controller will be available in the whole app and no duplicate controllers will be created.
Arguments: No additional arguments.
Pubspec Dependencies: Not required, signature package is natively available.
The Singleton class for SignatureController creation will look like this:
class SignatureControllerSingleton {
static final SignatureControllerSingleton _singleton =
SignatureControllerSingleton._internal();
factory SignatureControllerSingleton() {
return _singleton;
}
SignatureControllerSingleton._internal();
SignatureController? signatureController;
init() {
signatureController = SignatureController(
penStrokeWidth: 5,
penColor: Colors.red,
exportPenColor: Colors.red,
exportBackgroundColor: Colors.white,
);
}
}
This will be placed below the initSignatureController
function (after the closing parenthesis)
To personalize your Signature widget, you can adjust settings like penStrokeWidth
and exportPenColor
. If you’re interested in making further changes, the official documentation on pub.dev provides a complete list of modifiable properties.
This init()
method will be called from our main initSignatureController
function.
Future initSignatureController() async {
SignatureControllerSingleton().init();
}
The whole code should look like this:
// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'package:signature/signature.dart';
Future initSignatureController() async {
SignatureControllerSingleton().init();
}
class SignatureControllerSingleton {
static final SignatureControllerSingleton _singleton =
SignatureControllerSingleton._internal();
factory SignatureControllerSingleton() {
return _singleton;
}
SignatureControllerSingleton._internal();
SignatureController? signatureController;
init() {
signatureController = SignatureController(
penStrokeWidth: 5,
penColor: Colors.red,
exportPenColor: Colors.red,
exportBackgroundColor: Colors.white,
);
}
}
This custom action needs to be invoked directly from the main.dart
file. We do this to ensure all initializations such as our custom Signature Controller, take place right when the application starts up.
Navigate to “Custom Files” and open up main.dart
. On the right-hand side, you’ll find a section called “Final Actions.”
Go ahead and click on the “+” icon there. From the options that appear, select “initSignatureController.” Doing so will automatically update the code in main.dart
for you.
Now you can update your SignatureWidget
and update at line 35 where the TODO comment was created:
_signatureController = SignatureControllerSingleton().signatureController!;
Now, using this approach, you can easily tap into the same controller from other custom actions to carry out a variety of tasks.
Make sure to Compile Code at this point to remove all compilation errors
Arguments: No additional arguments.
Pubspec Dependencies: Not required.
To avoid compilation errors related to the Singleton class we’ve created, make sure to include a specific import in your files where you’re accessing the SignatureControllerSingleton
class. (So any file that requires the SignatureController singleton instance) This will ensure that the class is recognized and accessible.
import '/custom_code/actions/init_signature_controller.dart';
The custom action body is pretty simple now and the complete code looks like this:
// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import '/custom_code/actions/init_signature_controller.dart';
Future clearSignature() async {
SignatureControllerSingleton().signatureController!.clear();
}
With this custom action in place, you’re now free to control the Signature widget from anywhere in your app using the Action Flow Editor.
Want to extend the functionality for ‘undo’ and ‘redo’? Just create two more custom actions and adjust their code accordingly.
undoSignature
:
Future undoSignature() async {
SignatureControllerSingleton().signatureController!.undo();
}
redoSignature
:
Future redoSignature() async {
SignatureControllerSingleton().signatureController!.redo();
}
Often, you’ll want to save this signature in a database or display it on another screen as an image. To do so, converting it to Base64 format will be key.
Here’s how the code for that function would look:
Return Value: String (Nullable)
Arguments: No additional arguments.
Pubspec Dependencies: Not required.
// Automatic FlutterFlow imports
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import '/custom_code/actions/init_signature_controller.dart';
import 'dart:convert';
import 'dart:typed_data';
Future<String?> convertToBase64() async {
final Uint8List? uint8list =
await SignatureControllerSingleton().signatureController!.toPngBytes();
String? imageEncoded = uint8list != null ? base64.encode(uint8list) : null;
return imageEncoded;
}
And that’s a wrap! Feel free to use this same logic not only for other custom widgets that need controllers, but also whenever you want to control your widget via different custom actions.
As mentioned earlier, feel free to explore this public project to see everything in action for yourself.