Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Created January 25, 2024 16:57
Show Gist options
  • Save hawkkiller/2cf72e99770104ee3ddb240ae3cb73a5 to your computer and use it in GitHub Desktop.
Save hawkkiller/2cf72e99770104ee3ddb240ae3cb73a5 to your computer and use it in GitHub Desktop.
Async validation
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MainApp());
}
class UsernameValidator {
final UserRepository _userRepository;
UsernameValidator({
UserRepository? userRepository,
}) : _userRepository = userRepository ?? UserRepository();
// Validate username
// This returns true if the username is valid, false otherwise
Future<bool> checkUsernameIsUnique(String usrname) async {
try {
final isUnique = await _userRepository.checkNameUnique(usrname);
return isUnique;
} on Object catch (e) {
// logger.error(e); report error to logger and tracking system
// Logic to handle error; return true so that the user can continue
return true;
}
}
}
class UserRepository {
/// Returns true if value is unique, false otherwise
Future<bool> checkNameUnique(String value) async {
await Future.delayed(const Duration(seconds: 1));
if (value == 'Michelle') {
return false;
}
return true;
}
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorSchemeSeed: Colors.deepPurple,
),
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
/* Username */
late final ValueNotifier<String?> usernameError;
late final TextEditingController usernameController;
late final UsernameValidator usernameValidator;
/* Password */
late final ValueNotifier<String?> passwordError;
late final TextEditingController passwordController;
/* Button */
late final ValueNotifier<bool> inProgress;
@override
void initState() {
/* Username */
usernameController = TextEditingController();
usernameError = ValueNotifier(null);
usernameValidator = UsernameValidator();
/* Password */
passwordController = TextEditingController();
passwordError = ValueNotifier(null);
/* Button */
inProgress = ValueNotifier(false);
super.initState();
}
Future<bool> _validateFields() async {
var username = _validateUsername(usernameController.text);
var password = _validatePassword(passwordController.text);
passwordError.value = password;
username ??= await _validateUsernameUnique(usernameController.text);
usernameError.value = username;
return username == null && password == null;
}
String? _validateUsername(String username) {
if (username.length < 6) {
return 'Username must be at least 6 characters long';
}
if (!RegExp(r'^[a-zA-Z0-9]+$').hasMatch(username)) {
return 'Username must only contain alphanumeric characters';
}
return null;
}
Future<String?> _validateUsernameUnique(String username) async {
final isUnique = await usernameValidator.checkUsernameIsUnique(username);
if (!isUnique) {
return 'Username is already taken';
}
return null;
}
String? _validatePassword(String password) {
if (password.length < 8) {
return 'Password must be at least 8 characters long';
}
return null;
}
Future<void> _onSignUp() async {
inProgress.value = true;
final res = await _validateFields();
inProgress.value = false;
if (res) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sign up successful!'),
),
);
}
return;
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sign up failed!'),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints.loose(
const Size.fromWidth(400),
),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Builder(
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Sign Up',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: ValueListenableBuilder(
valueListenable: usernameError,
builder: (context, value, __) {
return TextField(
controller: usernameController,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
labelText: 'Username',
border: const OutlineInputBorder(),
errorText: value,
),
);
}),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: ValueListenableBuilder(
valueListenable: passwordError,
builder: (context, value, __) {
return TextField(
controller: passwordController,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
labelText: 'Password',
border: const OutlineInputBorder(),
errorText: value,
),
);
}),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Username should be at least 6 characters long and only contain alphanumeric characters. '
'Password should be at least 8 characters long.',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.outline,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: ValueListenableBuilder(
valueListenable: inProgress,
builder: (context, progress, _) {
return FilledButton.icon(
label: const Text('Send'),
onPressed: progress ? null : _onSignUp,
icon: progress
? const CircularProgressIndicator.adaptive()
: const Icon(Icons.send),
);
},
),
),
],
);
},
),
),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment