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:
- A Flutter development environment set up
- A Twilio account with Phone Verify service enabled
- The
twilio_phone_verify
package (version 2.0.0 or later) added to yourpubspec.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
instanceTextEditingController
s 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.