flutter_pinput

repository·master·Indexed 21 days ago

https://github.com/tkko/flutter_pinput

A highly customizable pin code input field widget for Flutter applications. It supports animated decorations, SMS autofill for iOS and Android, custom cursors, and form validation. The library provides the Pinput widget for standard implementations and Pinput.builder for full control over individual pin digit rendering, with styling managed via the PinTheme class across six different states.

Tokens
3.3K
Snippets
11
Records
13
Agent score
74%

What's inside flutter_pinput

  1. How Pinput states and PinTheme work

    master

    The Pinput widget transitions between 6 states: default, focused, submitted, following, disabled, and error. You can customize the appearance of each state using the PinTheme class and passing specific theme parameters to the Pinput widget.

    PinTheme Properties

    PropertyTypeDefault
    widthdouble56.0
    heightdouble60.0
    textStyleTextStyleTextStyle()
    marginEdgeInsetsGeometry-
    paddingEdgeInsetsGeometry-
    constraintsBoxConstraints-

    To avoid repeating code, you can create a defaultPinTheme and use copyDecorationWith or copyWith to derive other state themes (like focusedPinTheme or submittedPinTheme).

    final defaultPinTheme = PinTheme(
      width: 56,
      height: 56,
      textStyle: TextStyle(fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600),
      decoration: BoxDecoration(
        border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
        borderRadius: BorderRadius.circular(20),
      ),
    );
    
    final focusedPinTheme = defaultPinTheme.copyDecorationWith(
      border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
      borderRadius: BorderRadius.circular(8),
    );
    
    final submittedPinTheme = defaultPinTheme.copyWith(
      decoration: defaultPinTheme.decoration.copyWith(
        color: Color.fromRGBO(234, 239, 243, 1),
      ),
    );
    
    return Pinput(
      defaultPinTheme: defaultPinTheme,
      focusedPinTheme: focusedPinTheme,
      submittedPinTheme: submittedPinTheme,
      onCompleted: (pin) => print(pin),
    );
  2. Customize iOS Launch Screen Assets

    master

    To change the launch screen image for the iOS version of the app, replace the existing image files in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.

    Alternatively, you can manage these assets using Xcode:

    1. Open the iOS workspace using open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog to replace the current launch images.
    open ios/Runner.xcworkspace
  3. Configure SMS Autofill on iOS and Android

    master

    iOS

    SMS Autofill works out of the box by tapping the code displayed above the keyboard on a real device.

    Android

    Using Firebase Auth

    If using firebase_auth, you must manually set the controller's value within the verificationCompleted callback:

    await FirebaseAuth.instance.verifyPhoneNumber(
      verificationCompleted: (PhoneAuthCredential credential) {
        pinController.setText(credential.smsCode);
      },
      // ... other callbacks
    );

    Without Firebase Auth

    You can use the SMS Retriever API or SMS User Consent API. It is recommended to use the SmartAuth package as a wrapper.

    • SMS Retriever API: Requires including your App Signature in the SMS message (e.g., Your code is: 123456 kg+TZ3A5qzS). This allows automatic application without user interaction.
    • SMS User Consent API: Does not require an App Signature, but the user will be prompted to confirm reading the message.
  4. Get started with the Pinput example app

    master
    The example directory contains a complete Flutter application demonstrating the usage of the pinput package. You can use this project as a starting point to understand how to implement pin input fields in your own Flutter applications. For more specific implementation patterns, refer to the templates provided within the example's lib directory.
  5. Handle tap outside to unfocus Pinput

    master

    To improve user experience, you can use onTapOutside or onTapUpOutside to unfocus the Pinput widget when a user taps elsewhere in the application. This is useful for dismissing the keyboard when the user interacts with other parts of the UI.

    // Example of unfocusing when tapping outside
    Pinput(
      onTapOutside: (event) {
        focusNode.unfocus();
      },
      // Or more specifically for tap up
      onTapUpOutside: (event) {
        if (event.position == tapPosition) _focusNode.unfocus();
        tapPosition = null;
      },
    );
  6. Manage Pinput state with Controller and FocusNode

    master

    To programmatically control the value or focus of the Pinput widget, use a TextEditingController and a FocusNode.

    Important: You must manually dispose of both the controller and the focus node in your widget's dispose() method to prevent memory leaks.

    @override
    void dispose() {
      controller.dispose();
      focusNode.dispose();
      super.dispose();
    }
    final controller = TextEditingController();
    final focusNode = FocusNode();
    
    // In your build method
    Pinput(
      controller: controller,
      focusNode: focusNode,
    );
  7. Basic usage of Pinput

    master

    To implement a standard pin code input field, use the Pinput widget. You can handle the completion of the pin entry via the onCompleted callback.

    Widget buildPinPut() {
      return Pinput(
        onCompleted: (pin) => print(pin),
      );
    }
  8. Manage Pinput with TextEditingController

    master

    Use a TextEditingController to programmatically control the pin input.

    Note: Do not call setText, append, or delete inside a build method; call them in response to events or lifecycle methods.

    • pinController.setText('1222'): Sets the full pin.
    • pinController.append('1', 4): Appends a character (useful for custom keyboards).
    • pinController.delete(): Deletes the last character.
    final pinController = TextEditingController();
    
    // Usage
    pinController.setText('1222');
    pinController.append('1', 4);
    pinController.delete();
    
    return Pinput(
      controller: pinController,
    );
  9. Implement Form Validation in Pinput

    master

    Pinput supports validation through a validator function and integration with Flutter's Form widget.

    Key Properties

    • validator: A function (String) -> String? that returns an error message if the pin is invalid, or null if valid.
    • pinputAutovalidateMode: Controls when validation occurs. Use PinputAutovalidateMode.onSubmit to validate after the user taps the keyboard 'done' button or completes the pin.
    • forceErrorState: If true, the error state is applied regardless of the validator's return value.
    • errorText: A manual error message displayed under the Pinput.
    final formKey = GlobalKey<FormState>();
    
    return Form(
      key: formKey,
      child: Pinput(
        pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
        validator: (pin) {
          if (pin == '2224') return null;
          return 'Pin is incorrect';
        },
      ),
    );
    
    // To trigger validation manually:
    formKey.currentState!.validate();
  10. Manage Focus with FocusNode

    master

    Use a FocusNode to programmatically request or remove focus from the Pinput widget.

    final pinputFocusNode = FocusNode();
    
    // Usage
    pinputFocusNode.requestFocus();
    pinputFocusNode.unfocus();
    
    return Pinput(
      focusNode: pinputFocusNode,
    );
  11. Customize pin items with Pinput.builder

    master

    If the default pin item styling is insufficient, use the Pinput.builder constructor. This allows you to provide a PinItemWidgetBuilder to gain full control over the widget rendered for each individual pin digit.

    Note that when using the builder, many theme properties (like defaultPinTheme, focusedPinTheme, etc.) are not available via the constructor because you are responsible for rendering the items yourself.

    Pinput.builder(
      builder: (context, group, isFirst, isLast) {
        return MyCustomPinItem(isFocused: group.isFocused);
      },
      length: 6,
      onCompleted: (value) => print(value),
    );
  12. Configure PinTheme for styling pin fields

    master

    The PinTheme class is used to define the visual appearance of individual pin input fields in a Pinput widget. It allows you to customize dimensions, typography, spacing, and decoration for various states (default, focused, submitted, following, disabled, and error).

    Available Properties

    PropertyTypeDescription
    widthdouble?The width of each field
    heightdouble?The height of each field
    textStyleTextStyle?The text style for the pin. Defaults to the subhead text style from the current Theme if null.
    marginEdgeInsetsGeometry?Empty space surrounding the field container.
    paddingEdgeInsetsGeometry?Empty space inside the field container (e.g., space between border and text).
    constraintsBoxConstraints?Additional constraints applied to each field container.
    decorationBoxDecoration?The box decoration for the field. Properties like color, border, and borderRadius are implicitly animated when changed.

    Theme Manipulation Methods

    • apply({required PinTheme theme}): Merges the current theme with another, using the current theme's values as overrides.
    • copyWith({...}): Creates a new PinTheme instance by overriding specific properties.
    • copyDecorationWith({...}): Creates a new PinTheme by updating specific properties of the existing decoration (e.g., color, image, border, borderRadius, boxShadow, gradient, backgroundBlendMode, shape).
    • copyBorderWith({required Border border}): A specialized method to update only the border of the existing decoration.
    const PinTheme(
      width: 50,
      height: 50,
      textStyle: TextStyle(fontSize: 20, color: Colors.black),
      decoration: BoxDecoration(
        color: Colors.grey,
        borderRadius: BorderRadius.all(Radius.circular(8)),
        border: Border.all(color: Colors.blue),
      ),
    );