In this article, I’m going to explain how to create a flutter app with events. In the events approach, the best thing is that we can trigger many events as required from the UI and the event trigger will do the work in the background. It’s like triggering a set of background processes.
Let’s build a flutter app from the scratch. I’m using VS Code but feel free to use any editor as you wish. Also, I have the current latest build of flutter which is flutter 2.8.1 and dart 2.14.4. You might feel slight differences if you’re having different versions. Please feel free to comment on them as well here.
I have added the project to GitHub if you need to refer to it. Use the link https://github.com/rashintha/blog_flutter_event_approach
So, create a new flutter application to begin. (In the second step, choose Application)
Dependencies
You will require the following dependencies for this method. Please add the latest versions of them in your pubspec.yaml
. I have put the versions that I have used in creating this app.
- flutter_bloc: ^8.0.1
- equatable: ^2.0.3
How does the flutter events app work?
In a flutter event application, there’re three major components.
- Event (which is to be triggered by the UI)
- Listener (in this case the bloc)
- State
When the user interacts with the flutter UI, the UI will trigger events that we have created. When an event is triggered, a listener is available to catch the event trigger and do the work in the background. That listener and all the processing are done in the bloc file. Once the processing is done, the bloc is updating the state of the application which will make an automatic regeneration of the UI.
In this way, the programmer is able to create a structured flutter application and a UI which will not be stuck when handling a process.
Folder Structure
I’m using a folder structure like follows, use something you feel comfortable with as your structure.
Let’s Code
We first need to create a screen hander that is going to handle all the views of the application.
Create a file named screen_handler.dart
in the views folder and add the following code.
import 'package:flutter/cupertino.dart'; class ScreenHandler extends StatefulWidget { const ScreenHandler({Key? key}) : super(key: key); @override _ScreenHandlerState createState() => _ScreenHandlerState(); } class _ScreenHandlerState extends State { @override Widget build(BuildContext context) { return const Text("Screen Handler"); } }
Then let’s create a login screen for the app views/login/login_screen.dart
and add the following code.
import 'package:flutter/material.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State { @override Widget build(BuildContext context) { return const Scaffold( body: Text("Login"), ); } }
Now we need an event, bloc, and state to be created for the screen handler so we can display the login screen at the beginning.
Create an event file in the path blocs/screen/screen_event.dart
and create events as follows.
import 'package:equatable/equatable.dart'; abstract class ScreenHandlerEvent extends Equatable { const ScreenHandlerEvent([List props = const []]); } class ShowLoginScreen extends ScreenHandlerEvent { const ShowLoginScreen(); @override List<Object> get props => throw UnimplementedError(); @override String toString() => 'Show Login Screen'; }
Then add an enum
file to the utils
folder and add the following enums.
enum Screens { none, login, home }
Then create the state file in the path blocs/screen/screen_state.dart
and add the following code.
import 'package:flutter_event_approach/utils/barrell.dart'; class ScreenHandlerState { final Screens screen; final Screens? previousScreen; ScreenHandlerState({required this.screen, this.previousScreen}); factory ScreenHandlerState.loginScreen() { return ScreenHandlerState(screen: Screens.login); } ScreenHandlerState copyWith({Screens? screen}) { return ScreenHandlerState(screen: screen ?? this.screen); } @override String toString() { return '''ScreenHandlerState { screen: $screen, }'''; } }
In the state, the factory
is the initial state of the state.
And the last one is the bloc file. Create it in the path blocs/screen/screen_bloc.dart
and add the following code.
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/screen/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; class ScreenHandlerBloc extends Bloc<ScreenHandlerEvent, ScreenHandlerState> { ScreenHandlerBloc() : super(ScreenHandlerState.loginScreen()) { on( (event, emit) async => {emit(state.copyWith(screen: Screens.login))}); } }
Now change the main.dart
file as follows and provide the created bloc for all the screens.
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/screen/barrell.dart'; import 'package:flutter_event_approach/views/screen_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiBlocProvider(providers: [ BlocProvider( create: (BuildContext context) => ScreenHandlerBloc()) ], child: const ScreenHandler())); } }
Then change the screen_handler.dart
as follows to add handles for primary screens.
import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/screen/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; import 'package:flutter_event_approach/views/login/login_screen.dart'; class ScreenHandler extends StatefulWidget { const ScreenHandler({Key? key}) : super(key: key); @override _ScreenHandlerState createState() => _ScreenHandlerState(); } class _ScreenHandlerState extends State { late ScreenHandlerBloc _screenHandlerBloc; @override void initState() { super.initState(); _screenHandlerBloc = BlocProvider.of(context); } @override Widget build(BuildContext context) { return BlocBuilder( bloc: _screenHandlerBloc, builder: (BuildContext context, ScreenHandlerState screenHandlerState) { return Stack(children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 500), child: _primaryScreen(screenHandlerState), ) ]); }); } Widget _primaryScreen(ScreenHandlerState screenHandlerState) { if (screenHandlerState.screen == Screens.login) { return const LoginScreen(); } return const Text("No Screen"); } }
If you run the application, now you’ll be able to see your login screen now.
To get a good idea of the code, let’s create a login validation and a home screen so we can trigger a login event when the user presses the login button and if the details are verified, change the screen to the home screen.
Let’s first update the login_screen.dart
as follows.
import 'package:flutter/material.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State { late TextEditingController usernameController; late TextEditingController passwordController; @override void initState() { super.initState(); usernameController = TextEditingController(); passwordController = TextEditingController(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: Center( child: Stack( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 24), child: Center( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: const EdgeInsets.only(bottom: 32), child: const Align( child: Text( "Login", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24), ), alignment: Alignment.topCenter, ), ), TextFormField( controller: usernameController, textAlign: TextAlign.left, decoration: const InputDecoration( hintText: "User Name", hintStyle: TextStyle( color: Colors.black38, fontSize: 14), border: InputBorder.none), ), TextFormField( controller: passwordController, textAlign: TextAlign.left, obscureText: true, decoration: const InputDecoration( hintText: "Password", hintStyle: TextStyle( color: Colors.black38, fontSize: 14), border: InputBorder.none), ), GestureDetector( child: const Text( "LOGIN", style: TextStyle(fontWeight: FontWeight.bold), ), onTap: () => {}) ], ), ))) ], )))); } }
Now let’s create a set of a new event, bloc, and state for authentication. Create an event file in the path blocs/auth/auth_event.dart and create a login event as follows.
import 'package:equatable/equatable.dart'; abstract class AuthEvent extends Equatable { const AuthEvent([List props = const []]); } class Login extends AuthEvent { const Login({required this.username, required this.password}); final String username; final String password; @override List<Object> get props => throw UnimplementedError(); @override String toString() => 'Login'; }
Then update the enums.dart
with the following.
enum Screens { none, login, home } enum UserStatus { loggedOut, invalidUser, loggedIn }
And create the state in the path blocs/auth/auth_state.dart and add the following code.
import 'package:flutter_event_approach/utils/barrell.dart'; class AuthState { final UserStatus status; AuthState({required this.status}); factory AuthState.loadDefault() { return AuthState(status: UserStatus.loggedOut); } AuthState copyWith({UserStatus? status}) { return AuthState(status: status ?? this.status); } @override String toString() { return '''AuthState { status: $status }'''; } }
Now we need to create a new event to show the home screen in thee screen_event.dart
as follows.
import 'package:equatable/equatable.dart'; abstract class ScreenHandlerEvent extends Equatable { const ScreenHandlerEvent([List props = const []]); } class ShowLoginScreen extends ScreenHandlerEvent { const ShowLoginScreen(); @override List<Object> get props => throw UnimplementedError(); @override String toString() => 'Show Login Screen'; } class ShowHomeScreen extends ScreenHandlerEvent { const ShowHomeScreen(); @override List<Object> get props => throw UnimplementedError(); @override String toString() => 'Show Login Screen'; }
Then update the screen_bloc.dart
with the event listener code as follows.
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/screen/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; class ScreenHandlerBloc extends Bloc<ScreenHandlerEvent, ScreenHandlerState> { ScreenHandlerBloc() : super(ScreenHandlerState.loginScreen()) { on( (event, emit) async => {emit(state.copyWith(screen: Screens.login))}); on( (event, emit) async => {emit(state.copyWith(screen: Screens.home))}); } }
And we need to create a view for the home screen in the path views/home/home_screen.dart
as follows.
import 'package:flutter/material.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: const [ Center( child: Text("Home Screen"), ) ], )); } }
Now let’s create the bloc file in the path blocs/auth/auth_bloc.dart
and add the following code.
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; class AuthBloc extends Bloc<AuthEvent, AuthState> { AuthBloc({required this.screenHandlerBloc}) : super(AuthState.loadDefault()) { on((event, emit) async => {emit(await _mapLoginToState(event.username, event.password))}); } final ScreenHandlerBloc screenHandlerBloc; Future _mapLoginToState(String username, String password) async { if (username == 'admin' && password == '123') { screenHandlerBloc.add(const ShowHomeScreen()); return state.copyWith(status: UserStatus.loggedIn); } return state.copyWith(status: UserStatus.invalidUser); } }
And update the main.dart
with the newly created auth bloc as follows.
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/barrell.dart'; import 'package:flutter_event_approach/views/screen_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiBlocProvider(providers: [ BlocProvider( create: (BuildContext context) => ScreenHandlerBloc()), BlocProvider( create: (BuildContext context) => AuthBloc( screenHandlerBloc: BlocProvider.of(context))) ], child: const ScreenHandler())); } }
The update the login_screen.dart
with the auth event trigger as follows.
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({Key? key}) : super(key: key); @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State { late TextEditingController usernameController; late TextEditingController passwordController; late AuthBloc _authBloc; @override void initState() { super.initState(); _authBloc = BlocProvider.of(context); usernameController = TextEditingController(); passwordController = TextEditingController(); } @override Widget build(BuildContext context) { return Scaffold( body: BlocBuilder( bloc: _authBloc, builder: (BuildContext context, AuthState authState) { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: Center( child: Stack( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 24), child: Center( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: const EdgeInsets.only(bottom: 32), child: const Align( child: Text( "Login", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24), ), alignment: Alignment.topCenter, ), ), TextFormField( controller: usernameController, textAlign: TextAlign.left, decoration: const InputDecoration( hintText: "User Name", hintStyle: TextStyle( color: Colors.black38, fontSize: 14), border: InputBorder.none), ), TextFormField( controller: passwordController, textAlign: TextAlign.left, obscureText: true, decoration: const InputDecoration( hintText: "Password", hintStyle: TextStyle( color: Colors.black38, fontSize: 14), border: InputBorder.none), ), GestureDetector( child: const Text( "LOGIN", style: TextStyle( fontWeight: FontWeight.bold), ), onTap: () => { _authBloc.add(Login( username: usernameController.text, password: passwordController.text)) }), authState.status == UserStatus.invalidUser ? Container( padding: const EdgeInsets.only(top: 8), child: const Text( "Invalid User", style: TextStyle(color: Colors.red), ), ) : Container() ], ), ))) ], ))); })); } }
Now we have to update the screen_handler.dart
with the home screen details as follows.
import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_event_approach/blocs/screen/barrell.dart'; import 'package:flutter_event_approach/utils/barrell.dart'; import 'package:flutter_event_approach/views/home/home_screen.dart'; import 'package:flutter_event_approach/views/login/login_screen.dart'; class ScreenHandler extends StatefulWidget { const ScreenHandler({Key? key}) : super(key: key); @override _ScreenHandlerState createState() => _ScreenHandlerState(); } class _ScreenHandlerState extends State { late ScreenHandlerBloc _screenHandlerBloc; @override void initState() { super.initState(); _screenHandlerBloc = BlocProvider.of(context); } @override Widget build(BuildContext context) { return BlocBuilder( bloc: _screenHandlerBloc, builder: (BuildContext context, ScreenHandlerState screenHandlerState) { return Stack(children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 500), child: _primaryScreen(screenHandlerState), ) ]); }); } Widget _primaryScreen(ScreenHandlerState screenHandlerState) { if (screenHandlerState.screen == Screens.login) { return const LoginScreen(); } else if (screenHandlerState.screen == Screens.home) { return const HomeScreen(); } return const Text("No Screen"); } }
Now run the program and type user name and password to login and you will see your home screen.
So that’s it
If you have any suggestions or questions about this guide, comment here or send me an email.
Great content! Keep up the good work!
Thank you so much for the comment! It’s really valuable to me. Keep in touch with my blog