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.

You can either clone the complete project from this link or continue reading for a detailed breakdown.

What we'll cover:

  • Crafting a Signature widget from the ground up using FlutterFlow's Custom Widget feature.

  • The Signature widget needs a SignatureController that will also be used to perform other related actions such as clearing signature, undo etc. We will cover how to create a single instance of the SignatureController so all our custom actions can access it.

  • Creating Custom Actions for tasks like clearing the signature, undoing actions, redoing actions, and converting the signature to Base64 format.

Custom Widget: Signature Widget

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!

Custom Action: initSignatureController

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

Custom Action: clearSignature

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();
}

Custom Action: convertToBase64

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.


Developer Terms Singleton* It's a special kind of class in programming where only one instance (or copy) of the class can exist at any time.

Last updated