Implement BLoC Pattern With TextField

In this post we will learn to implement BLoC pattern with TextField of a Flutter app. We will use also use StreamController to receive user input and StreamHandler to show an error message if input is not valid.

Introduction

BLoC pattern is the preferred State Management pattern when developing mobile apps with Flutter. If you are completely new to BLoC pattern, you should checkout our other post on BLoC pattern with flutter.

Basically a BLoC pattern encourages separation of business logic from the user interface layer.

DecisionMentor app

In this post we will create a TextField and handle it’s onChanged event using a stream controller implementing the BLoC pattern.

Setup

Let’s start off by creating a Flutter Material app with a single TextField.

import 'package:flutter/material.dart';

class MyTextFieldApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            backgroundColor: Colors.white,
            body: Container(
                padding: EdgeInsets.all(24.0),
                child: Center(
                  child: TextField(
                    decoration: InputDecoration(
                      fillColor: Colors.grey[100],
                      filled: true,
                      enabledBorder: OutlineInputBorder(
                        borderSide: BorderSide(
                          color: Colors.grey,
                        ),
                        borderRadius: BorderRadius.circular(6.0),
                      ),
                      focusedBorder: OutlineInputBorder(
                        borderSide:
                            BorderSide(color: Colors.blueAccent, width: 0.0),
                        borderRadius: BorderRadius.circular(6.0),
                      ),
                      errorBorder: OutlineInputBorder(
                        borderSide: BorderSide(color: Colors.red, width: 0.0),
                        borderRadius: BorderRadius.circular(6.0),
                      ),
                      focusedErrorBorder: OutlineInputBorder(
                        borderSide:
                            BorderSide(color: Colors.red, width: 0.0),
                        borderRadius: BorderRadius.circular(6.0),
                      ),
                    ),
                  ),
                ))));
  }
}

We have added some basic InputDecoration on this TextField as well.

See our previous post about learning to decorate a TexField here.

The output for the code above:

Single TextField
Single TextField

Setup A BLoC For The TextField

A BLoC is nothing but just another class. We will create a class named TextBloc which will look like this:

import 'dart:async';

class TextBloc {
  var _textController = StreamController<String>();
  Stream<String> get textStream => _textController.stream;


  dispose() {
    _textController.close();
  }
}

This class has couple of things:

  1. StreamController
    • This is responsible for adding data and error events on it’s stream.
  2. Stream
    • Stream of string data.

Now to add data to the stream controller, we will also need a public function.

//TextBloc class
...
updateText(String text) {
    (text == null || text == "")
        ? _textController.sink.addError("Invalid value entered!")
        : _textController.sink.add(text);
  }
...

Implementing BLoC With TextField

Now we can use the TextBloc in our app by first creating an instance of this bloc and using the updateText method during the onChanged event.

//init TextBloc
TextBloc _textBloc = TextBloc();
//implement the updateText method
...
Center(
	  child: TextField(
		onChanged: (String text) => _textBloc.updateText(text),
		decoration: InputDecoration(
...

As you can see, whenever a user enters value for the TextField, this value gets passed onto the StreamController inside our BLoC.

Learn To Use ValueNotifier Instead Of Streams

Showing Errors On TextField

Now that we have a working BLoC for our TextField, next we want to show errors whenever user enters invalid values.

In order to achieve this, we will have to listen to the text stream for the data entered by the user. For this purpose, we will make use of the StreamBuilder widget.

class MyTextFieldApp extends StatelessWidget {
  final TextBloc _textBloc = TextBloc();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            backgroundColor: Colors.white,
            body: Container(
                padding: EdgeInsets.all(24.0),
                child: Center(
                  child: StreamBuilder(
                      stream: _textBloc.textStream,
                      builder: (ctxt, AsyncSnapshot<String> textStream) {
                        return TextField(
                          onChanged: (String text) =>
                              _textBloc.updateText(text),
                          decoration: InputDecoration(
                            errorText: textStream.hasError ? textStream.error : null,
                            fillColor: Colors.grey[100],
                            filled: true,
                            enabledBorder: OutlineInputBorder(
                              borderSide: BorderSide(
                                color: Colors.grey,
                              ),
                              borderRadius: BorderRadius.circular(6.0),
                            ),
                            focusedBorder: OutlineInputBorder(
                              borderSide: BorderSide(
                                  color: Colors.blueAccent, width: 0.0),
                              borderRadius: BorderRadius.circular(6.0),
                            ),
                            errorBorder: OutlineInputBorder(
                              borderSide:
                                  BorderSide(color: Colors.red, width: 0.0),
                              borderRadius: BorderRadius.circular(6.0),
                            ),
                            focusedErrorBorder: OutlineInputBorder(
                              borderSide: BorderSide(
                                  color: Colors.redAccent, width: 0.0),
                              borderRadius: BorderRadius.circular(6.0),
                            ),
                          ),
                        );
                      }),
                ))));
  }
}

Final Output:

Showing Error On TextField
Showing Error On TextField