Table of Contents
In this article of Flutter UI, we will explore how to implement a user interface in Flutter for adding a new credit card and displaying a list of credit cards. Flutter is a powerful open-source UI software development kit created by Google, which allows developers to build cross-platform applications with a single codebase. We’ll walk through the steps to create a visually appealing and user-friendly interface to handle credit card details efficiently.
Understanding the Importance of a User-Friendly Flutter UI
A well-designed user interface is crucial for any mobile application, especially when dealing with sensitive information like credit card details. Users need a seamless experience while adding new cards and viewing their existing ones. A clutter-free and intuitive UI instills trust and encourages users to engage with the app more frequently.
Setting Up the Flutter Project
Before diving into the UI implementation, ensure you have Flutter installed and set up on your system. Create a new Flutter project and configure it for your target platform(s).
Creating the Add New Credit Card Screen Flutter UI
Designing the Layout
Begin by designing the flutter UI for the “Add New Credit Card” screen. Use appropriate form fields for capturing cardholder name, card number, expiration date, and CVV. Incorporate labels and placeholders to guide users effectively.
Implementing Form Validation
Implement form validation to ensure users enter accurate and complete card details. Validate the card number using algorithms like Luhn’s algorithm and check for valid expiration dates.
Saving Card Details
Once the form is validated, provide a “Save” button to store the credit card details in a secure manner. Consider encrypting sensitive information before saving it locally or on a server.
Building the Credit Card List Screen
Displaying Saved Cards
Create a separate screen to display the list of saved credit cards. Use a ListView to showcase the cards efficiently. Include cardholder names and the last four digits of each card for easy identification.
Adding Edit and Delete Functionality
Allow users to edit card details if needed. Implement a functionality to update the card information while maintaining data integrity. Additionally, provide an option to delete a saved card securely.
Improving Flutter UI Responsiveness
Optimize the UI to work flawlessly across different screen sizes and orientations. Ensure that all elements are correctly aligned and adequately spaced.
class CreditCardsPage extends StatefulWidget { final userName = "example"; final countCreditCards = 2; const CreditCardsPage({Key? key}) : super(key: key); @override _CreditCardsPageState createState() => _CreditCardsPageState(); } class _CreditCardsPageState extends State<CreditCardsPage> { List<CreditCardsModel> list = []; GlobalKey<ScaffoldState> scaffoldState = new GlobalKey<ScaffoldState>(); TextStyle style = TextStyle(fontFamily: 'Montserrat', fontSize: 20.0); bool isLoading = false; void initState() { super.initState(); list.add(CreditCardsModel( cardExpiration: "10/25", cardHolder: "Card Holder 1", cardNumber: "5450 7879 4864 5555", cvv: 456, bankName: "Axis Bank")); list.add(CreditCardsModel( cardExpiration: "10/25", cardHolder: "Card Holder 2", cardNumber: "5450 7879 8457 7854", cvv: 455, bankName: "Sbi Bank")); list.add(CreditCardsModel( cardExpiration: "10/25", cardHolder: "Card Holder 3", cardNumber: "5450 7879 8457 7854", cvv: 455, bankName: "Sbi Bank")); list.add(CreditCardsModel( cardExpiration: "10/25", cardHolder: "Card Holder 3", cardNumber: "5450 7879 8457 7854", cvv: 455, bankName: "Sbi Bank")); } Widget propertyListTileCards(int index) { return Container( child: Column( children: [ CreditCard( cardNumber: list[index].cardNumber, cardExpiry: list[index].cardExpiration, cardHolderName: list[index].cardHolder, cvv: list[index].cvv.toString(), bankName: list[index].bankName.toString(), showBackSide: false, width: MediaQuery.of(context).size.width, frontBackground: list[index].bankName == "Axis Bank" ? CardBackgrounds.black : CardBackgrounds.custom(0xFF2979FF), backBackground: CardBackgrounds.white, showShadow: false, ), Divider() ], ), ); } @override Widget build(BuildContext context) { return Scaffold( key: scaffoldState, body: isLoading ? Container() : SingleChildScrollView( child: Container( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ SafeArea( child: Padding( padding: EdgeInsets.only(left: 16, right: 16, top: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( " Linked Credit Cards ", style: TextStyle( fontSize: 20, color: Colors.blueGrey), ), Container( padding: EdgeInsets.only( left: 8, right: 8, top: 0, bottom: 0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), color: Colors.pink[50], ), child: Row( children: <Widget>[ IconButton( icon: Icon(Icons.settings), onPressed: () => {}, ), ], ), ) ], ))), Container( child: ListView.builder( physics: ClampingScrollPhysics(), scrollDirection: Axis.vertical, shrinkWrap: true, itemCount: list.length, itemBuilder: (BuildContext context, int index) { return Container( color: (index % 2 == 0) ? Colors.transparent : Colors.transparent, child: propertyListTileCards(index)); }), ), // for(int i = 0; i< widget.countCreditCards; i++) // //if(i == 0) // CreditCard( // cardNumber: list[i].cardNumber, // cardExpiry: list[i].cardExpiration, // cardHolderName: list[i].cardHolder, // cvv: list[i].cvv.toString(), // bankName: list[i].bankName.toString(), // showBackSide: false, // frontBackground: // list[i].bankName == "Axis Bank" // ? CardBackgrounds.black // : CardBackgrounds.custom(0xFF2979FF), // backBackground: CardBackgrounds.white, // showShadow: true, // ), // _buildCreditCardAxis(), // SizedBox( // height: 15, // ), // _buildCreditCardSbi(), ], ), ), ), floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () => { Navigator.pop(context, false), Navigator.push(context, MaterialPageRoute(builder: (_) => AddCreditCardPage())), }, tooltip: 'Add Credit Card', child: const Icon(Icons.add), backgroundColor: Colors.blueAccent, ), ])); } Widget _buildCreditCardAxis() { // if(index == 0) return CreditCard( cardNumber: "5450 7879 4864 5555", cardExpiry: "10/25", cardHolderName: "Card Holder 1", cvv: "456", bankName: "Axis Bank", showBackSide: false, frontBackground: CardBackgrounds.black, backBackground: CardBackgrounds.white, showShadow: true, ); // if(index == 1) // SizedBox( // height: 15, // ); // if(index == 1) // return CreditCard( // cardNumber: "5450 7879 8457 7854", // cardExpiry: "12/35", // cardHolderName: "Card Holder 2", // cvv: "556", // bankName: "Sbi Bank", // showBackSide: false, // frontBackground: CardBackgrounds.custom(0xFF2979FF), // backBackground: CardBackgrounds.white, // showShadow: true, // ); //return Container(); } Widget _buildCreditCardSbi() { return CreditCard( cardNumber: "5450 7879 8457 7854", cardExpiry: "10/25", cardHolderName: "Card Holder 2", cvv: "456", bankName: "Sbi Bank", showBackSide: false, frontBackground: CardBackgrounds.custom(0xFF2979FF), backBackground: CardBackgrounds.white, showShadow: true, ); } // Build the top row containing logos Row _buildLogosBlock() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Image.asset( "assets/images/contact_less.png", height: 20, width: 18, ), Image.asset( "assets/images/mastercard.png", height: 50, width: 50, ), ], ); } }
class AddCreditCardPage extends StatefulWidget { AddCreditCardPage({Key? key}) : super(key: key); //final String title; @override _AddCreditCardPageState createState() => _AddCreditCardPageState(); } class _AddCreditCardPageState extends State<AddCreditCardPage> { String cardNumber = ''; String cardHolderName = ''; String expiryDate = ''; String cvv = ''; bool showBack = false; String bankName = ''; late FocusNode _focusNode; TextEditingController cardNumberCtrl = TextEditingController(); TextEditingController expiryFieldCtrl = TextEditingController(); TextEditingController bankFieldCtrl = TextEditingController(); @override void initState() { super.initState(); _focusNode = FocusNode(); _focusNode.addListener(() { setState(() { _focusNode.hasFocus ? showBack = true : showBack = false; }); }); } @override void dispose() { _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Add Credit Card"), actions: [], ), body: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ SizedBox( height: 40, ), CreditCard( cardNumber: cardNumber, cardExpiry: expiryDate, cardHolderName: cardHolderName, cvv: cvv, bankName: bankName, showBackSide: showBack, frontBackground: CardBackgrounds.black, backBackground: CardBackgrounds.white, showShadow: true, // mask: getCardTypeMask(cardType: CardType.americanExpress), ), SizedBox( height: 40, ), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Container( margin: EdgeInsets.symmetric( horizontal: 20, ), child: TextFormField( controller: cardNumberCtrl, decoration: InputDecoration(hintText: 'Card Number'), maxLength: 16, onChanged: (value) { final newCardNumber = value.trim(); var newStr = ''; final step = 4; for (var i = 0; i < newCardNumber.length; i += step) { newStr += newCardNumber.substring( i, math.min(i + step, newCardNumber.length)); if (i + step < newCardNumber.length) newStr += ' '; } setState(() { cardNumber = newStr; }); }, ), ), Container( margin: EdgeInsets.symmetric( horizontal: 20, ), child: TextFormField( controller: bankFieldCtrl, decoration: InputDecoration(hintText: 'Bank Name'), maxLength: 5, onChanged: (value) { var newDateValue = value.trim(); setState(() { bankFieldCtrl.text = newDateValue; bankFieldCtrl.selection = TextSelection.fromPosition( TextPosition(offset: newDateValue.length)); bankName = newDateValue; }); }, ), ), Container( margin: EdgeInsets.symmetric( horizontal: 20, ), child: TextFormField( controller: expiryFieldCtrl, decoration: InputDecoration(hintText: 'Card Expiry'), maxLength: 5, onChanged: (value) { var newDateValue = value.trim(); final isPressingBackspace = expiryDate.length > newDateValue.length; final containsSlash = newDateValue.contains('/'); if (newDateValue.length >= 2 && !containsSlash && !isPressingBackspace) { newDateValue = newDateValue.substring(0, 2) + '/' + newDateValue.substring(2); } setState(() { expiryFieldCtrl.text = newDateValue; expiryFieldCtrl.selection = TextSelection.fromPosition( TextPosition(offset: newDateValue.length)); expiryDate = newDateValue; }); }, ), ), Container( margin: EdgeInsets.symmetric( horizontal: 20, ), child: TextFormField( decoration: InputDecoration(hintText: 'Card Holder Name'), onChanged: (value) { setState(() { cardHolderName = value; }); }, ), ), Container( margin: EdgeInsets.symmetric(horizontal: 20, vertical: 25), child: TextFormField( decoration: InputDecoration(hintText: 'CVV'), maxLength: 3, onChanged: (value) { setState(() { cvv = value; }); }, focusNode: _focusNode, ), ), ], ) ], ), ), ); } }
Handling Errors and Edge Cases
Consider potential error scenarios, such as network failures or incorrect inputs, and provide helpful error messages to guide users through the correction process.