Twilio Phone Verification in Flutter

In this comprehensive guide, we’ll walk through the process of implementing Twilio phone verification in flutter. We’ll cover every aspect of the implementation, including the full code and detailed explanations.

Prerequisites

Before we begin, ensure you have:

  1. A Flutter development environment set up
  2. A Twilio account with Phone Verify service enabled
  3. The twilio_phone_verify package (version 2.0.0 or later) added to your pubspec.yaml file:
dependencies:
  flutter:
    sdk: flutter
  twilio_phone_verify: ^2.0.0

Full Implementation

Let’s break down the entire implementation, explaining each part in detail:

1. Main App Structure

import 'package:flutter/material.dart';
import 'package:twilio_phone_verify/twilio_phone_verify.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Twilio Phone Verify',
      theme: ThemeData(
        primaryColor: Color(0xFF233659),
      ),
      home: PhoneVerification(),
    );
  }
}

This section sets up the main app structure. We import the necessary packages, define the main() function, and create a MyApp widget that serves as the root of our application. The app uses a custom primary color and sets the home page to our PhoneVerification widget.

2. Phone Verification State Management

enum VerificationState { enterPhone, enterSmsCode }

class PhoneVerification extends StatefulWidget {
  @override
  _PhoneVerificationState createState() => _PhoneVerificationState();
}

class _PhoneVerificationState extends State<PhoneVerification> {
  TwilioPhoneVerify _twilioPhoneVerify;

  var verificationState = VerificationState.enterPhone;
  var phoneNumberController = TextEditingController();
  var smsCodeController = TextEditingController();
  bool loading = false;
  String errorMessage;
  String successMessage;

  @override
  void initState() {
    super.initState();
    _twilioPhoneVerify = TwilioPhoneVerify(
        accountSid: '',
        serviceSid: '',
        authToken: '');
  }

Here, we define an enum VerificationState to manage the two states of our verification process: entering the phone number and entering the SMS code. The PhoneVerification widget is stateful, allowing us to manage the verification process.

In the state class, we initialize:

  • TwilioPhoneVerify instance
  • TextEditingControllers for phone number and SMS code inputs
  • Variables to manage loading state, error messages, and success messages

In the initState() method, we create the TwilioPhoneVerify instance with your Twilio credentials (remember to fill in your actual credentials).

3. Build Method and State Management

  @override
  Widget build(BuildContext context) {
    return verificationState == VerificationState.enterPhone
        ? _buildEnterPhoneNumber()
        : _buildEnterSmsCode();
  }

  void changeErrorMessage(var message) =>
      setState(() => errorMessage = message);

  void changeSuccessMessage(var message) =>
      setState(() => successMessage = message);

  void changeLoading(bool status) => setState(() => loading = status);

  void switchToSmsCode() async {
    changeSuccessMessage(null);
    changeErrorMessage(null);
    changeLoading(false);
    setState(() {
      verificationState = VerificationState.enterSmsCode;
    });
  }

  void switchToPhoneNumber() {
    if (loading) return;
    changeSuccessMessage(null);
    changeErrorMessage(null);
    setState(() {
      verificationState = VerificationState.enterPhone;
    });
  }

The build() method determines which screen to show based on the verificationState. We also define helper methods to manage the app’s state, including changing error and success messages, updating the loading state, and switching between the phone number and SMS code screens.

4. Sending Verification Code

  void sendCode() async {
    if (phoneNumberController.text.isEmpty || loading) return;
    changeLoading(true);
    TwilioResponse twilioResponse =
        await _twilioPhoneVerify.sendSmsCode(phoneNumberController.text);

    if (twilioResponse.successful) {
      changeSuccessMessage('Code sent to ${phoneNumberController.text}');
      await Future.delayed(Duration(seconds: 1));
      switchToSmsCode();
    } else {
      changeErrorMessage(twilioResponse.errorMessage);
    }
    changeLoading(false);
  }

This method sends the verification code to the entered phone number. It checks for empty input or if a request is already in progress, sends the code using Twilio’s API, and handles the response accordingly.

5. Verifying SMS Code

  void verifyCode() async {
    if (phoneNumberController.text.isEmpty ||
        smsCodeController.text.isEmpty ||
        loading) return;
    changeLoading(true);
    TwilioResponse twilioResponse = await _twilioPhoneVerify.verifySmsCode(
        phone: phoneNumberController.text, code: smsCodeController.text);
    if (twilioResponse.successful) {
      if (twilioResponse.verification.status == VerificationStatus.approved) {
        changeSuccessMessage('Phone number is approved');
      } else {
        changeSuccessMessage('Invalid code');
      }
    } else {
      changeErrorMessage(twilioResponse.errorMessage);
    }
    changeLoading(false);
  }

This method verifies the SMS code entered by the user. It checks for empty inputs or ongoing requests, verifies the code using Twilio’s API, and handles the response, updating the UI accordingly.

6. UI for Phone Number Input

  _buildEnterPhoneNumber() {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(40.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: phoneNumberController,
              keyboardType: TextInputType.phone,
              decoration: InputDecoration(labelText: 'Enter Phone Number'),
            ),
            SizedBox(height: 20),
            Container(
              width: double.infinity,
              height: 40,
              child: TextButton(
                  onPressed: sendCode,
                  style: TextButton.styleFrom(
                      backgroundColor: Theme.of(context).primaryColor),
                  child: loading
                      ? _loader()
                      : Text(
                          'Send code',
                          style: TextStyle(color: Colors.white),
                        )),
            ),
            if (errorMessage != null) ...[
              SizedBox(height: 30),
              _errorWidget()
            ],
            if (successMessage != null) ...[
              SizedBox(height: 30),
              _successWidget()
            ]
          ],
        ),
      ),
    );
  }

This method builds the UI for entering the phone number. It includes a text field for the phone number, a button to send the code, and displays error or success messages when applicable.

7. UI for SMS Code Input

  _buildEnterSmsCode() {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.transparent,
        leading: IconButton(
          icon: Icon(
            Icons.arrow_back_ios,
            size: 18,
            color: Theme.of(context).primaryColor,
          ),
          onPressed: switchToPhoneNumber,
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(40.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: smsCodeController,
              keyboardType: TextInputType.number,
              decoration: InputDecoration(labelText: 'Enter Sms Code'),
            ),
            SizedBox(height: 20),
            Container(
              width: double.infinity,
              height: 40,
              child: TextButton(
                  onPressed: verifyCode,
                  style: TextButton.styleFrom(
                      backgroundColor: Theme.of(context).primaryColor),
                  child: loading
                      ? _loader()
                      : Text(
                          'Verify',
                          style: TextStyle(color: Colors.white),
                        )),
            ),
            if (errorMessage != null) ...[
              SizedBox(height: 30),
              _errorWidget()
            ],
            if (successMessage != null) ...[
              SizedBox(height: 30),
              _successWidget()
            ]
          ],
        ),
      ),
    );
  }

This method builds the UI for entering the SMS code. It includes a back button to return to the phone number input, a text field for the SMS code, a verify button, and displays error or success messages when applicable.

8. Helper Widgets

  _loader() => SizedBox(
        height: 15,
        width: 15,
        child: CircularProgressIndicator(
          strokeWidth: 2,
          valueColor: AlwaysStoppedAnimation(Colors.white),
        ),
      );

  _errorWidget() => Material(
        borderRadius: BorderRadius.circular(5),
        color: Colors.red.withOpacity(.1),
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
          child: Row(
            children: [
              Expanded(
                  child: Text(
                errorMessage,
                style: TextStyle(color: Colors.red),
              )),
              IconButton(
                  icon: Icon(Icons.close, size: 16),
                  onPressed: () => changeErrorMessage(null))
            ],
          ),
        ),
      );

  _successWidget() => Material(
        borderRadius: BorderRadius.circular(5),
        color: Colors.green.withOpacity(.1),
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
          child: Row(
            children: [
              Expanded(
                  child: Text(
                successMessage,
                style: TextStyle(color: Colors.green),
              )),
              IconButton(
                  icon: Icon(Icons.close, size: 16),
                  onPressed: () => changeSuccessMessage(null))
            ],
          ),
        ),
      );

These helper methods create reusable widgets for the loading indicator, error messages, and success messages, enhancing the user experience and code reusability.

Complete Code

For your convenience, here’s the complete code for the Flutter Twilio Phone Verify implementation:

import 'package:flutter/material.dart';
import 'package:twilio_phone_verify/twilio_phone_verify.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Twilio Phone Verify',
      theme: ThemeData(
        primaryColor: Color(0xFF233659),
      ),
      home: PhoneVerification(),
    );
  }
}

enum VerificationState { enterPhone, enterSmsCode }

class PhoneVerification extends StatefulWidget {
  @override
  _PhoneVerificationState createState() => _PhoneVerificationState();
}

class _PhoneVerificationState extends State<PhoneVerification> {
  TwilioPhoneVerify _twilioPhoneVerify;

  var verificationState = VerificationState.enterPhone;
  var phoneNumberController = TextEditingController();
  var smsCodeController = TextEditingController();
  bool loading = false;
  String errorMessage;
  String successMessage;

  @override
  void initState() {
    super.initState();
    _twilioPhoneVerify = TwilioPhoneVerify(
        accountSid: '',
        serviceSid: '',
        authToken: '');
  }

  @override
  Widget build(BuildContext context) {
    return verificationState == VerificationState.enterPhone
        ? _buildEnterPhoneNumber()
        : _buildEnterSmsCode();
  }

  void changeErrorMessage(var message) =>
      setState(() => errorMessage = message);

  void changeSuccessMessage(var message) =>
      setState(() => successMessage = message);

  void changeLoading(bool status) => setState(() => loading = status);

  void switchToSmsCode() async {
    changeSuccessMessage(null);
    changeErrorMessage(null);
    changeLoading(false);
    setState(() {
      verificationState = VerificationState.enterSmsCode;
    });
  }

  void switchToPhoneNumber() {
    if (loading) return;
    changeSuccessMessage(null);
    changeErrorMessage(null);
    setState(() {
      verificationState = VerificationState.enterPhone;
    });
  }

  void sendCode() async {
    if (phoneNumberController.text.isEmpty || loading) return;
    changeLoading(true);
    TwilioResponse twilioResponse =
        await _twilioPhoneVerify.sendSmsCode(phoneNumberController.text);

    if (twilioResponse.successful) {
      changeSuccessMessage('Code sent to ${phoneNumberController.text}');
      await Future.delayed(Duration(seconds: 1));
      switchToSmsCode();
    } else {
      changeErrorMessage(twilioResponse.errorMessage);
    }
    changeLoading(false);
  }

  void verifyCode() async {
    if (phoneNumberController.text.isEmpty ||
        smsCodeController.text.isEmpty ||
        loading) return;
    changeLoading(true);
    TwilioResponse twilioResponse = await _twilioPhoneVerify.verifySmsCode(
        phone: phoneNumberController.text, code: smsCodeController.text);
    if (twilioResponse.successful) {
      if (twilioResponse.verification.status == VerificationStatus.approved) {
        changeSuccessMessage('Phone number is approved');
      } else {
        changeSuccessMessage('Invalid code');
      }
    } else {
      changeErrorMessage(twilioResponse.errorMessage);
    }
    changeLoading(false);
  }

  _buildEnterPhoneNumber() {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(40.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: phoneNumberController,
              keyboardType: TextInputType.phone,
              decoration: InputDecoration(labelText: 'Enter Phone Number'),
            ),
            SizedBox(
              height: 20,
            ),
            Container(
              width: double.infinity,
              height: 40,
              child: TextButton(
                  onPressed: sendCode,
                  style: TextButton.styleFrom(
                      backgroundColor: Theme.of(context).primaryColor),
                  child: loading
                      ? _loader()
                      : Text(
                          'Send code',
                          style: TextStyle(color: Colors.white),
                        )),
            ),
            if (errorMessage != null) ...[
              SizedBox(
                height: 30,
              ),
              _errorWidget()
            ],
            if (successMessage != null) ...[
              SizedBox(
                height: 30,
              ),
              _successWidget()
            ]
          ],
        ),
      ),
    );
  }

  _buildEnterSmsCode() {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.transparent,
        leading: IconButton(
          icon: Icon(
            Icons.arrow_back_ios,
            size: 18,
            color: Theme.of(context).primaryColor,
          ),
          onPressed: switchToPhoneNumber,
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(40.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: smsCodeController,
              keyboardType: TextInputType.number,
              decoration: InputDecoration(labelText: 'Enter Sms Code'),
            ),
            SizedBox(
              height: 20,
            ),
            Container(
              width: double.infinity,
              height: 40,
              child: TextButton(
                  onPressed: verifyCode,
                  style: TextButton.styleFrom(
                      backgroundColor: Theme.of(context).primaryColor),
                  child: loading
                      ? _loader()
                      : Text(
                          'Verify',
                          style: TextStyle(color: Colors.white),
                        )),
            ),
            if (errorMessage != null) ...[
              SizedBox(
                height: 30,
              ),
              _errorWidget()
            ],
            if (successMessage != null) ...[
              SizedBox(
                height: 30,
              ),
              _successWidget()
            ]
          ],
        ),
      ),
    );
  }

  _loader() => SizedBox(
        height: 15,
        width: 15,
        child: CircularProgressIndicator(
          strokeWidth: 2,
          valueColor: AlwaysStoppedAnimation(Colors.white),
        ),
      );

  _errorWidget() => Material(
        borderRadius: BorderRadius.circular(5),
        color: Colors.red.withOpacity(.1),
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
          child: Row(
            children: [
              Expanded(
                  child: Text(
                errorMessage,
                style: TextStyle(color: Colors.red),
              )),
              IconButton(
                  icon: Icon(
                    Icons.close,
                    size: 16,
                  ),
                  onPressed: () => changeErrorMessage(null))
            ],
          ),
        ),
      );

  _successWidget() => Material(
        borderRadius: BorderRadius.circular(5),
        color: Colors.green.withOpacity(.1),
        child: Padding(
          padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
          child: Row(
            children: [
              Expanded(
                  child: Text(
                successMessage,
                style: TextStyle(color: Colors.green),
              )),
              IconButton(
                  icon: Icon(
                    Icons.close,
                    size: 16,
                  ),
                  onPressed: () => changeSuccessMessage(null))
            ],
          ),
        ),
      );
}

Conclusion

This comprehensive guide covers the entire implementation of phone number verification in a Flutter app using Twilio’s Phone Verify service. By following this guide and understanding each component, you can successfully integrate phone verification into your Flutter application, enhancing its security and user authentication process.

Remember to handle edge cases, implement proper error handling, and consider additional security measures like rate limiting to create a robust and secure verification system.

Summary
Twilio Phone Verification in Flutter
Article Name
Twilio Phone Verification in Flutter
Description
In this comprehensive guide, we'll walk through the process of implementing Twilio phone verification in flutter.
Author
Publisher Name
raheemdev.com
Publisher Logo

Leave a Comment