What you use is Abstraction

What if I told you that everything we use to build software is nothing but an abstraction? The BLOC architecture we use in Flutter is also an abstraction.I used the BLOC architecture before the BLOC library even existed. I know how hard it was to write…


This content originally appeared on Level Up Coding - Medium and was authored by Jiten Patel

What if I told you that everything we use to build software is nothing but an abstraction? The BLOC architecture we use in Flutter is also an abstraction.

I used the BLOC architecture before the BLOC library even existed. I know how hard it was to write all that boilerplate code, and I understand how the BLOC library helps to eliminate such boilerplate code.

In this article, I aim to discuss the BLOC state management library and explain how “what you use is an abstraction”.

Flutter developers are well-versed in the Bloc architecture, but for those who are new to Flutter, Bloc helps resolve complex state management issues. In my opinion, it is a pure state management library that helps us separate presentation from business logic, making our code faster, easier to test, and reusable.

Before we go further, I have written an article titled “How to Use the Dart Stream API in Flutter,” which serves as the foundation for this article. Before diving into this, please read that article to understand what a stream is and how it works. A Github repo for below code can be found here!

Before the BLOC library

Before BLOC was even a library, we managed states using Streams. Here’s how we used to write a bloc from scratch:

  1. Create Event class
  2. Create State class
  3. Create BLOC class
  4. Create View class

Event class

abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

State class

class CounterState {
final int counter;

CounterState(this.counter);
}

BLOC class

import 'dart:async';

import 'package:counter_app/counter_event.dart';
import 'package:counter_app/counter_state.dart';

class CounterBloc {
int _counter = 0;

// Stream controllers
final _stateController = StreamController<CounterState>();

// Event controller
final _eventController = StreamController<CounterEvent>();

// Output Stream
Stream<CounterState> get state => _stateController.stream;

// Input Sink
Sink<CounterEvent> get event => _eventController.sink;

CounterBloc() {
_stateController.add(CounterState(_counter)); // Initial state
_eventController.stream.listen(
(event) => _stateController.add(
_mapEventToState(event),
),
);
}

CounterState _mapEventToState(CounterEvent event) {
if (event is IncrementEvent) {
_counter++;
} else if (event is DecrementEvent) {
_counter--;
}

return CounterState(_counter);
}

void dispose() {
_stateController.close();
_eventController.close();
}
}

The Event and State classes are pretty self-explanatory. The main class is the CounterBloc, where the main logic of the app resides.

We create two instances of StreamController: one for the state and another for events. We have one output stream, state, and one input sink, event. To understand what streams and sinks do, please read my article here.

In the early days of writing a Bloc architecture, we used to map events to states. The _mapEventToState method does the same thing; it maps events to states. Here, I have used an if-else statement because I only have two events, IncrementEvent and DecrementEvent. If I had more than two events, I would have used a switch case.

Here’s how we use the CounterBloc class in our view:

View class

import 'package:counter_app/counter_bloc.dart';
import 'package:counter_app/counter_event.dart';
import 'package:counter_app/counter_state.dart';
import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
const CounterPage({super.key});

@override
State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
final _counterBloc = CounterBloc();

@override
void dispose() {
_counterBloc.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter Page"),
),
body: Center(
child: StreamBuilder<CounterState>(
stream: _counterBloc.state,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Counter value: ${snapshot.data?.counter ?? 0}',
style: const TextStyle(fontSize: 24.0),
),
const SizedBox(height: 16.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () =>
_counterBloc.event.add(IncrementEvent()),
child: const Text('Increment'),
),
const SizedBox(width: 16.0),
ElevatedButton(
onPressed: () =>
_counterBloc.event.add(DecrementEvent()),
child: const Text('Decrement'),
),
],
),
],
);
} else {
return const CircularProgressIndicator();
}
},
),
),
);
}
}

We use the StreamBuilder to listen to the stream, build the UI, and rebuild the UI.

The above code seems easy because we only have a single state and two events. The problem occurs when we have multiple events and multiple states. Imagine having multiple stream controllers for events and states and ensuring they are properly disposed of to prevent memory leaks. Doesn’t that sound like a hectic task?

This is where the BLOC library comes into the picture. This is the problem the Bloc library solves. You don’t need to worry about creating and managing streams. You just need to focus on how many states and events your application has.

If you ever had a deeper look into Bloc library it does the same thing as above. In the Bloc library, the StreamController is used to manage state streams efficiently. Here’s a snippet of code from the Bloc library:

Image by Author

In this code:

  • StreamController<State>.broadcast() creates a broadcast stream controller for managing state updates. The broadcast stream allows multiple listeners to receive the same stream events.
  • The _state variable holds the current state of the Bloc.
  • The _emitted boolean is used internally to track if the state has been emitted.
  • The state getter provides access to the current state.
  • The stream getter returns the stream of state changes, allowing listeners to subscribe to updates.
  • The isClosed getter indicates whether the Bloc has been closed. Once a Bloc is closed (by calling close), it can no longer emit new states, ensuring that no state changes can occur after the Bloc’s lifecycle has ended.

This approach simplifies state management by handling stream creation, management, and disposal internally, reducing boilerplate code and potential errors related to stream handling.

Here’s another snippet from the Bloc library:

Image by Author

In this code:

  • The emit method is used to emit a new state. It first checks whether the Bloc is closed. If it is closed, it throws a StateError, preventing any new states from being emitted.
  • It then checks if the new state is the same as the current state and whether the state has already been emitted. If both conditions are true, it avoids unnecessary updates and returns early.
  • If the state is different, it triggers the onChange method to handle the state change, updates the _state variable, and adds the new state to the _stateController stream.
  • Finally, it sets _emitted to true to indicate that a new state has been emitted. If an error occurs during this process, it catches the error, passes it to the onError method, and rethrows it.

This approach illustrates the concept of abstraction by encapsulating the complexity of state management within the emitmethod. Developers can focus on defining state transitions without worrying about the underlying details of state emission, error handling, or stream management. This encapsulation simplifies code and reduces the likelihood of errors, showcasing the power of abstraction in software design.

You can find the complete Counter app implementation with bloc library here.

</> And this is what I mean by What you use is an Abstraction</>

Summary

This article explores the concept of abstraction in software development using Flutter’s Bloc architecture as an example. It discusses the challenges of manual Bloc implementation before the Bloc library existed and highlights how the Bloc library simplifies state management. By abstracting away complexities, the Bloc library reduces boilerplate code and enhances maintainability, demonstrating the power of abstraction in creating efficient and manageable code.

Related articles:

Before you leave

Did you know the clap 👏 counter goes up to 50?

Have any questions? Let’s get connected on LinkedIn, Twitter, Github & Instagram

Happy Coding { ❤ }


What you use is Abstraction was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Jiten Patel


Print Share Comment Cite Upload Translate Updates
APA

Jiten Patel | Sciencx (2024-07-30T15:01:22+00:00) What you use is Abstraction. Retrieved from https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/

MLA
" » What you use is Abstraction." Jiten Patel | Sciencx - Tuesday July 30, 2024, https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/
HARVARD
Jiten Patel | Sciencx Tuesday July 30, 2024 » What you use is Abstraction., viewed ,<https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/>
VANCOUVER
Jiten Patel | Sciencx - » What you use is Abstraction. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/
CHICAGO
" » What you use is Abstraction." Jiten Patel | Sciencx - Accessed . https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/
IEEE
" » What you use is Abstraction." Jiten Patel | Sciencx [Online]. Available: https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/. [Accessed: ]
rf:citation
» What you use is Abstraction | Jiten Patel | Sciencx | https://www.scien.cx/2024/07/30/what-you-use-is-abstraction/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.