Form Validation with Stream BloC and RxDart
In this article, we will explore the Form Validation with Stream BloC and RxDart. We will also implement a demo program and learn how to implement it in your flutter applications.
If you’re looking for the best Flutter app development company for your mobile application then feel free to contact us at — support@flutterdevs.com.
Introduction:
First of all, I would like to introduce you to Validation with Stream BloC and RxDart.
RxDart:
RxDart is an implementation of the popular reactive API for asynchronous programming, leveraging the native Dart Streams API. Changelog.
FlutterBloC:
Flutter Widgets that make it easy to implement the BLoC (Business Logic Component) design pattern. Built to be used with the bloc state management package. Widgets that make it easy to integrate blocs and cubits into Flutter. Built to work with package: bloc.
Implementation:
Let’s see how to Implement the form validation with stream bloc.
First Add these two dependencies in pubsec.yaml file
dependencies:
flutter_bloc: ^8.0.1
rxdart: ^0.27.3
Alright, now we will work on our stream part for this. First of all, we will create a new file and call it login_bloc_state.dart
part of 'login_bloc_cubit.dart';
abstract class LoginBloc {}
class LoginInitial extends LoginBloc {}
Secondly, we will create the login_bloc_cubit.dart file
Behavior Subject:
The BehaviorSubject is, by default, a broadcast (aka hot) controller, to fulfill the Rx Subject contract. This means the Subject’s stream can listen to multiple times.
Rx.combineLatest:
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element. Observables need to be an array. If the result selector is omitted, a list with the elements will be yielded.
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:rxdart/rxdart.dart';
part 'login_bloc_state.dart';
class LoginScreenCubit extends Cubit<LoginBloc> {
LoginScreenCubit() : super(LoginInitial());
final _userNameController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
Stream<String> get userNameStream => _userNameController.stream;
Stream<String> get passwordStream => _passwordController.stream;
void clearStreams() {
updateUserName('');
updatePassword('');
}
void updateUserName(String userName) {
if (userName.length < 4) {
_userNameController.sink.addError("Please enter at least 4 characters of your name here");
} else {
_userNameController.sink.add(userName);
}
}
void updatePassword(String password) {
if (password.length < 4) {
_passwordController.sink.addError("Please enter at least 4 character of the password here");
} else {
_passwordController.sink.add(password);
}
}
Stream<bool> get validateForm => Rx.combineLatest2(
userNameStream,
passwordStream,
(a, b,) => true,
);
}
Note: You can learn more about behavior subjects over here
Setup Bloc Providers file:-
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/login_bloc_cubit.dart';
List<BlocProvider> blocProviders = [
BlocProvider<LoginScreenCubit>(create: (context) => LoginScreenCubit()),
];
Setup main.dart file:-
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc_providers.dart';
import 'login_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: blocProviders,
child: const MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginScreen(),
),
);
}
}
Finally, we will design Login Screen UI:
Wrap TextFormField with StreamBuilder widget and provide stream to it.Invoke validator function from on Changed callback of TextFormField.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/login_bloc_cubit.dart';
import 'custom_widgets/custom_plain_button.dart';
import 'custom_widgets/custom_text_field.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
LoginScreenCubit? _loginScreenCubit;
@override
void initState() {
WidgetsBinding.instance?.addPostFrameCallback((_) {
_loginScreenCubit?.clearStreams();
});
super.initState();
}
@override
Widget build(BuildContext context) {
_loginScreenCubit = BlocProvider.of<LoginScreenCubit>(
context,
listen: false,
);
return Scaffold(
appBar: AppBar(
title: const Text('Validation with BloC'),
),
backgroundColor: Colors.white,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
child: Column(
children: [
Expanded(child: _buildMiddleView()),
_buildBottomButtonView()
],
),
),
),
);
}
_buildMiddleView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Login In',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30),
),
const SizedBox(
height: 10,
),
StreamBuilder(
stream: _loginScreenCubit?.userNameStream,
builder: (context, snapshot) {
return CustomTextField(
onChange: (text) {
_loginScreenCubit?.updateUserName(text);
},
labelText: 'Username',
textInputType: TextInputType.emailAddress,
);
}),
const SizedBox(
height: 10,
),
StreamBuilder(
stream: _loginScreenCubit?.passwordStream,
builder: (context, snapshot) {
return CustomTextField(
onChange: (text) {
_loginScreenCubit?.updatePassword(text);
},
labelText: 'Password',
textInputType: TextInputType.text,
isObscureText: true,
);
}),
const SizedBox(
height: 10,
),
],
);
}
_buildBottomButtonView() {
return StreamBuilder(
stream: _loginScreenCubit?.validateForm,
builder: (context, snapshot) {
return CustomPlainButton(
isEnabled: snapshot.hasData,
btnColor: snapshot.hasData ? Colors.red : Colors.grey,
height: 67,
onTap: snapshot.hasData ? _loginBtnTap : null,
label: 'Log in',
lblColor: Colors.white,
);
},
);
}
_loginBtnTap() {
print('Login Button Pressed');
}
}
When we run the application, we ought to get the screen’s output like the underneath screen capture.
- > Wrap TextFormField with StreamBuilder widget and provide stream to it:
StreamBuilder has 2 required parameters, stream and builder callback. StreamBuilder will listen to the provided stream and the builder function will facilitate us to build the UI accordingly.
StreamBuilder(
stream: _loginScreenCubit?.passwordStream,
builder: (context, snapshot) {
return CustomTextField(
onChange: (text) {
_loginScreenCubit?.updatePassword(text);
},
labelText: 'Password',
textInputType: TextInputType.text,
isObscureText: true,
);
}),
- > Invoke validator function from on Changed callback of TextFormField:
We’ll utilize the on Changed callback of TextFormField and invoke the validator method.
onChange: (text) {
_loginScreenCubit?.updatePassword(text);
},
Then, we will Making button enabled or Disable
We will use RxDart’s combined latest function to check if both streams have data, as a boolean, and again send it as a stream, to be able to utilize it to make the button enabled.
The new stream will emit true when we have passed all the validations by checking if both the streams have data and none has an error with it.
Stream<bool> get validateForm => Rx.combineLatest2(
userNameStream,
passwordStream,
(a, b,) => true,
);
Connecting the resultant stream to the button
Wrap the button with stream builder and provide the combined stream in the stream property.
StreamBuilder(
stream: _loginScreenCubit?.validateForm,
builder: (context, snapshot) {
return CustomPlainButton(
isEnabled: snapshot.hasData,
btnColor: snapshot.hasData ? Colors.red : Colors.grey,
height: 67,
onTap: snapshot.hasData ? _loginBtnTap : null,
label: 'Log in',
lblColor: Colors.white,
);
},
);
For making the button be enabled according to the validations, we will use the stream snapshots has data property to check if it has data and connects it to the buttons onTap property so that if it doesn’t have data we will set it onTap property to null, making button disabled.
onTap: snapshot.hasData ? _loginBtnTap : null,
When we run the application, we ought to get the screen’s output like the underneath screen capture.
❤ ❤ Thanks for reading this article ❤❤
If I got something wrong? Let me know in the comments. I would love to improve.
Clap 👏 If this article helps you.
GitHub Link:
find the source code of the validation_with_stream_bloc:
GitHub – flutter-devs/validation_with_stream_bloc
A new Flutter project. This project is a starting point for a Flutter application. A few resources to get you started…github.com
Feel free to connect with us:
And read more articles from FlutterDevs.com.
FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire a flutter developer for your cross-platform Flutter mobile app project on an hourly or full-time basis as per your requirement! You can connect with us on Facebook, GitHub, Twitter, and LinkedIn for any flutter-related queries.
We welcome feedback and hope that you share what you’re working on using #FlutterDevs. We truly enjoy seeing how you use Flutter to build beautiful, interactive web experiences.