Flutter Custom Text Controllers: Because Boring Inputs Are So Last Year

Prashant Nigam
7 min readAug 11, 2024

--

Ever get the feeling that plain old text fields just aren’t cutting it anymore? Do you find yourself longing for the days when input fields were quirky, delightful, and a little more… extra? Well, friend, you’re in luck! Today, we’re diving into the whimsical world of custom text editing controllers in Flutter. Because why settle for basic when you can have bespoke?

Why Create Custom Controllers?

Why, indeed? The answer is simple: because you’re awesome and your app deserves the best. Custom controllers let you inject personality into your input fields, like making sure those pesky integers behave, or formatting dates so perfectly that even your calendar app will be jealous.

Step 1: Setting Up the Project

First things first — if you don’t have a Flutter project, let’s fix that right now. Open up your terminal, stretch those fingers, and type:

flutter create custom_controller_demo
cd custom_controller_demo

Now you’ve got a project. Look at you, being all productive and stuff!

Step 2: Creating Custom Controllers

IntegerTextEditingController

Let’s kick things off with a custom controller that makes sure users enter real numbers — not the kind you made up in math class.

import 'package:flutter/material.dart';

/// A custom TextEditingController for handling integer input.
class IntegerTextEditingController extends TextEditingController {
/// Initializes the controller with an optional initial integer value.
IntegerTextEditingController({int? initialValue}) {
if (initialValue != null) {
setValue(initialValue);
}
// Add a listener to parse input changes
addListener(_parseInput);
}

int? _integerValue;

/// Gets the current integer value of the controller.
int? get integerValue => _integerValue;

/// Parses the input text to update the integer value.
void _parseInput() {
if (text.isEmpty) {
_integerValue = null;
} else {
final rawText = text.trim().replaceAll(',', '');
final intValue = int.tryParse(rawText);
if (intValue != null) {
_integerValue = intValue;
} else {
_integerValue = null;
}
}
}

/// Sets the integer value and updates the text.
void setValue(int value) {
_integerValue = value;
text = value.toString();
}
}

Why does this matter? Because your app isn’t some wishy-washy place where “2 + 2 = maybe 22.” You want integers, you get integers. It’s like having your own personal bouncer for your text fields — only numbers get in.

DoubleTextEditingController

Not all numbers are whole — just ask your grocery bill. Here’s a controller to keep your doubles in line:

/// A custom TextEditingController for handling double input.
class DoubleTextEditingController extends TextEditingController {
/// Initializes the controller with an optional initial double value.
DoubleTextEditingController({double? initialValue}) {
if (initialValue != null) {
setValue(initialValue);
}
// Add a listener to parse input changes
addListener(_parseInput);
}

double? _doubleValue;

/// Gets the current double value of the controller.
double? get doubleValue => _doubleValue;

/// Parses the input text to update the double value.
void _parseInput() {
if (text.isEmpty) {
_doubleValue = null;
} else {
final rawText = text.trim().replaceAll(',', '');
final doubleValue = double.tryParse(rawText);
if (doubleValue != null) {
_doubleValue = doubleValue;
} else {
_doubleValue = null;
}
}
}

/// Sets the double value and updates the text.
void setValue(double value) {
_doubleValue = value;
text = value.toString();
}
}

With this controller, your users can enter all the 3.14159s and 2.71828s their hearts desire. Because who are we to limit their love for decimals?

DateTextEditingController

Dates are tricky — they’re either flying by or dragging their feet. Let’s tame them with a custom controller:

import 'package:intl/intl.dart';

/// A custom TextEditingController for handling date and time input.
class DateTextEditingController extends TextEditingController {
/// Initializes the controller with an optional initial date and time.
DateTextEditingController({DateTime? date}) {
_dateTime = date;
_updateText();
}

DateTime? _dateTime;

/// Gets the current date and time of the controller.
DateTime? get dateTime => _dateTime;

/// Gets the current date in the format 'dd MMM yyyy'.
String get date => _dateTime != null ? DateFormat('dd MMM yyyy').format(_dateTime!) : '';

/// Gets the current time in the format 'hh:mm a'.
String get time => _dateTime != null ? DateFormat('hh:mm a').format(_dateTime!) : '';

/// Sets the date part of the controller's date and time.
void setDate(DateTime date) {
_dateTime = _dateTime != null
? _dateTime!.copyWith(year: date.year, month: date.month, day: date.day)
: DateTime(date.year, date.month, date.day);
_updateText();
}

/// Sets the time part of the controller's date and time.
void setTime(TimeOfDay time) {
_dateTime = _dateTime != null
? _dateTime!.copyWith(hour: time.hour, minute: time.minute)
: DateTime(0, 1, 1, time.hour, time.minute);
_updateText();
}

/// Sets the entire date and time of the controller.
void setDateTime(DateTime dateTime) {
_dateTime = dateTime;
_updateText();
}

/// Updates the text of the controller based on the current date and time.
void _updateText() {
text = _dateTime != null ? DateFormat('dd MMM yyyy').format(_dateTime!) : '';
}
}

Now you can format dates like a pro. You’re basically a time wizard. Just don’t let it go to your head.

You’ve made it this far, and now it’s time to put your skills to the test! We’ve got a special mission for you: create a custom text controller that can handle multiple values, like a secret agent handling multiple identities. so are you ready for mission?

Challenge: Unlock the Secret Code

Your task is to create a MultiValueTextEditingController that can store and retrieve multiple values, like a master key that unlocks a treasure chest of secrets.

Can you crack the code and create a controller that’s worthy of a secret agent?

your time start now

Woohoo! You’ve made it to the end of the mission! If you’re feeling like a total boss, a coding rockstar, and a master of complexity, then congratulations are in order! You’ve earned your batch as a MultiValueTextEditingController agent!

But, if you’re still stuck in the mission, don’t worry, agent! We’ve got your back. Here’s the solution to help you escape the complexity trap and emerge victorious:

class MultiValueTextEditingController extends TextEditingController {
MultiValueTextEditingController({String? key}) {
if (key != null) {
setKey(key);
}
}

String? _key;

String? get key => _key;

void setKey(String value) {
_key = value;
}
}

Step 3: Using the Custom Controllers in Your Flutter App

So, you’ve got these shiny new controllers — what now? Time to show them off! Here’s how you can use them in a simple (but totally awesome) Flutter screen:

import 'package:flutter/material.dart';
import 'package:pocket_tracker/utility/text_field_controller.dart';

class CustomControllerExampleScreen extends StatefulWidget {
const CustomControllerExampleScreen({super.key});

@override
State<CustomControllerExampleScreen> createState() =>
_CustomControllerExampleScreenState();
}

class _CustomControllerExampleScreenState
extends State<CustomControllerExampleScreen> {
final _formKey = GlobalKey<FormState>();

late final IntegerTextEditingController _integerController;
late final DoubleTextEditingController _doubleController;
late final DateTextEditingController _dateController;

@override
void initState() {
super.initState();
_integerController = IntegerTextEditingController();
_doubleController = DoubleTextEditingController();
_dateController = DateTextEditingController();
}

@override
void dispose() {
_integerController.dispose();
_doubleController.dispose();
_dateController.dispose();
super.dispose();
}

void _submitForm() {
if (_formKey.currentState!.validate()) {
// Use the controller's value to perform some action
print('Integer Value: ${_integerController.integerValue}');
print('Double Value: ${_doubleController.doubleValue}');
print('Selected Date: ${_dateController.dateTime}');
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Controller Example')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _integerController,
decoration:
const InputDecoration(labelText: 'Enter an Integer'),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null ||
value.isEmpty ||
_integerController.integerValue == null) {
return 'Please enter a valid integer';
}
return null;
},
),
TextFormField(
controller: _doubleController,
decoration: const InputDecoration(labelText: 'Enter a Double'),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value == null ||
value.isEmpty ||
_doubleController.doubleValue == null) {
return 'Please enter a valid double';
}
return null;
},
),
TextFormField(
controller: _dateController,
decoration: const InputDecoration(labelText: 'Select a Date'),
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (pickedDate != null) {
_dateController.setDate(pickedDate);
}
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _submitForm,
child: const Text('Submit'),
),
],
),
),
),
);
}
}

Congratulations on mastering the basics of custom text editing controllers in Flutter! But why stop there when you can take your skills to the next level?

Your Mission, Should You Choose to Accept It:

Create a custom text editing controller that leverages a model to store and manage data. Imagine this controller as a highly specialized tool that seamlessly handles input based on a predefined model. It could be anything from a form data handler to a controller that manages a set of related values. The sky’s the limit!

Guidelines:

  • Model Integration: Design your controller to interact with a model, allowing it to read and write data in a structured way.
  • Functionality: Think about how your controller can handle complex data, such as validating input based on the model’s constraints or updating multiple fields at once.
  • Creativity: Don’t be afraid to think outside the box! This is your chance to create something truly unique.

🚀 Pro Tip: I’ll be sharing my approach to solving this challenge in a new article within the next 4 to 5 days, so stay tuned for more insights!

Ready to Take on the Challenge?

If you’re up for the task, dive into your code editor and start building your model-based custom text editing controller. Whether you nail it on the first try or learn a lot along the way, this challenge is all about pushing your Flutter skills to the next level.

🎉 Mission Accomplished! 🎉

You’ve just unlocked the secrets of custom text editing controllers in Flutter, turning ordinary input fields into something extraordinary. Go ahead, give yourself a pat on the back, and maybe even treat yourself to your favorite snack. After all, you’re now equipped to make your app’s inputs as unique as your creativity allows. Keep coding, keep creating, and remember — in the world of Flutter, the possibilities are endless! 🚀

--

--

Prashant Nigam

A passionate software developer, getting experience with Flutter and Django. I love to code, be open-source, and am always open to facing new challenges.