Foreword
In the previous post I gave introduction to my idea of creating a C64 Emulator in Flutter.
I explained briefly how to install Flutter and configuring an IDE for using it and looked at a Hello World project created by IntelliJ.
In this post we will continue our journey on creating a C64 emulator in Flutter.
Introduction to using BloCs
BloC, Event and State
You might remember from the previous post, I was illustrating by means of a diagram explaining what the purpose of an Event and a State were.
An Event you would use, for instance, when you click a button in your flutter app and you want your BloC to react in a certain way. You would then trigger an Event when you click a button, and your BloC will listen for that event.
In response to the event, your BloC will do some processing and afterwards it might have some data you want to display in your widget. In this case, your Widget will emit State, which your BlocBuilder will use to construct a widget for display.
equatable: ^2.0.5pubspec.yaml is like a project file in Flutter hosting different settings and dependencies. I will not be going into details of the pubspec.yaml here. There is quite a few websites going into depth about this file. However, I will mention if there is things that needs to be added to this file for our project.
import 'package:equatable/equatable.dart'; class C64State extends Equatable { @override // TODO: implement props List<Object?> get props => throw UnimplementedError(); }Similarly we create an event class:
import 'package:equatable/equatable.dart'; class C64Event extends Equatable { @override // TODO: implement props List<Object?> get props => throw UnimplementedError(); }We are now ready to create our BloC, but first we need add the following dependency:
flutter_bloc: ^8.1.3Now we can create our BloC class:
import 'package:flutter_bloc/flutter_bloc.dart'; import 'c64_event.dart'; import 'c64_state.dart'; class C64Bloc extends Bloc<C64Event, C64State> { C64Bloc(): super(C64State()); }Our created classes looks fairly empty, but it will get more body as go along. One thing you will notice with our C64Bloc class is that we pass an instance of C64State() to our super. This is our initial state and will force a render of our Stateless widget. There will be subsequent state change for which we will use emit() to update our UI. More on this later.
class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo 2', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } }We will add the BlocProvider instance in the home: selector:
home: BlocProvider( create: (context) => C64Bloc(), child: const MyHomePage(title: 'Flutter Demo Home Page'), )So, in the create selector we create an instance of our Bloc which will be passed down our widget tree.
Having made the change, The IDE complains that MyHomeClass now needs a build method. So, we let the IDE implement this method. With all this our MyHomePage class implifies to the following:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { // TODO: implement build throw UnimplementedError(); } }The IDE has created a TODO for us. Let us implement it straight by making use of a BlockBuilder:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<C64Bloc, C64State>( builder: (BuildContext context, state) { return Text('Hi There!!'); }, ); } }And everything renders in the browser:
class C64State extends Equatable { final time = DateTime.now().millisecondsSinceEpoch; ... }We can now adjust our MyHomePage class as follows:
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return BlocBuilder<C64Bloc, C64State>( builder: (BuildContext context, state) { return Text(state.time.toString()); }, ); } }And the rendered page look like this:
Using Events
This is where events come in. When you click a button, an event will be fired which our BloC class will listen for and update the state.
Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('BLoC Example')), body: BlocBuilder<C64Bloc, C64State>( builder: (BuildContext context, state) { return Text(state.time.toString()); }, ), floatingActionButton: FloatingActionButton( tooltip: 'Step', onPressed: () { }, child: const Icon(Icons.arrow_forward), ) ); }With all this we get an appBar and button on the lower right of the screen:
... onPressed: () { context.read<C64Bloc>().add(C64Event()); }, ...This is in effect the shorthand for getting our injected C64Bloc instance and adding an event to it when we press the button.
class C64State extends Equatable { ... @override // TODO: implement props List<Object?> get props => throw UnimplementedError(); }Now this was part of the original code generated when I asked the IDE to implement the missing methods for me. Clearly, the IDE left me a TODO, which I ignored, but also purposefully ignored to illustrate a point 😁.
import 'package:equatable/equatable.dart'; class C64State extends Equatable { final int time = DateTime.now().millisecondsSinceEpoch; @override List<Object?> get props => [time]; }If we now recompile, we see everything works as expected, and with any click, the timestamp updates.