Flutter Search Using BLoC and Firestore

Carlo Tupa Indriauan
5 min readMay 24, 2021
https://www.byriza.com/lib/blog/flutter39.png

So a long story short, I was taking one of the courses at my university, especially majoring in computer science, namely software projects (Proyek Perangkat Lunak). As the name implies, I was given the task of creating a team of five people and making software with predetermined topics in this course. My team got the topic of creating mobile applications that are involved in academia, especially in the field of law. The name of the application is Pantau Peradilanmu. Well, in this article I will briefly explain how to make a search on flutter using bloc state management and firestore.

What is Bloc?

A Bloc is a more advanced class which relies on events to trigger state changes rather than functions. Bloc also extends BlocBase which means it has a similar public API as Cubit. However, rather than calling a function on a Bloc and directly emitting a new state, Blocs receive events and convert the incoming events into outgoing states.

What is Firestore?

Firestore is a flexible, scalable NoSQL cloud database to store and sync data. It keeps your data in sync across client apps through realtime listeners and offers offline support so you can build responsive apps that work regardless of network latency or Internet connectivity.

How I Implement the Search

Here I will not explain and show very complete each implementation that I do, but I will explain the important points that I did in making a search on the application.

The first step I did was create a function that would filter the collection in the firestore that will be used according to the keywords used in search.

So, in the firestore that I use, I have a collection of ‘pantau_peradilan’ which contains all the monitoring reports which we will filter based on the ‘nomor Search’ field. Then the search results obtained are then sorted based on the date the report was made from newest to oldest.

static Future<List<PantauLaporan>?> getSearchResult(
{DocumentSnapshot? start, required String qSearch}) async {
Query laporan = FirebaseFirestore.instance
.collection('pantau_peradilan')
.where('nomorSearch', isGreaterThanOrEqualTo: qSearch.toLowerCase())
.where('nomorSearch',
isLessThanOrEqualTo: qSearch.toLowerCase() + '\uf8ff');

QuerySnapshot result = await laporan.get();

List<PantauLaporan> listPL = result.docs.map((DocumentSnapshot document) {
Map<String, dynamic>? doc = document.data();

return PantauLaporan.fromJson(doc!, document.id, document);
}).toList();
listPL.sort((b, a) => a.createdAt.compareTo(b.createdAt));
return listPL;
}

After finishing creating functions related to firestore, the next step I did was to create the files needed to manage the Bloc state, namely event, state, and bloc.

I created an event which triggers the state when the user performs a search.

class PantauLaporanSearchEvent extends PantauLaporanEvent {
final String query;

const PantauLaporanSearchEvent(this.query);

@override
String toString() => 'PantauLaporanSearchEvent { query: $query }';
}
class PantauLaporanSearchState extends PantauLaporanState {
final List<PantauLaporan>? laporan;
final bool? hasReachedLimit;

PantauLaporanSearchState({this.laporan, this.hasReachedLimit})
: super(null, false);

PantauLaporanSearchState copyWith({
List<PantauLaporan>? laporan,
bool? hasReachedLimit,
}) {
return PantauLaporanSearchState(
laporan: laporan ?? this.laporan,
hasReachedLimit: hasReachedLimit ?? this.hasReachedLimit,
);
}

@override
List<Object> get props => [laporan!, hasReachedLimit!];
}

In the bloc file, I use the event I created earlier that occurs when the state search occurs.

if (event is PantauLaporanSearchEvent) {
laporan = await PantauLaporan.getSearchResult(qSearch: event.query);
if (laporan!.isEmpty) {
yield PantauLaporanLoaded(laporan: laporan, hasReachedLimit: true);
} else {
yield PantauLaporanSearchState(
laporan: laporan,
hasReachedLimit: true,
);
}

Then I created a search text field on the page that contains all the monitoring reports.

SearchTextField(
controller: textEditingController..text = typedSearch!,
hint: "Cari nomor perkara..",
onChange: (val) {
typedSearch = val;
if (isSearching) {
setState(() {
isSearching = false;
});
}
},
error: null,
suffix: IconButton(
icon: isSearching ? Icon(Icons.clear) : Icon(Icons.search),
onPressed: () {
if (isSearching) {
typedSearch = "";
setState(() {
isSearching = false;
});
} else {
if (typedSearch == "") {
} else {
setState(() {
isSearching = true;
isPressed = false;
});
bloc!..add(PantauLaporanSearchEvent(typedSearch!));
}
}
},
),
),

In the search that I made, I implemented it after I finished searching, the icon changed to x and there is a button that can be used to delete all search results so that they return to the way they were before the search.

Container(child:
BlocBuilder<PantauLaporanBloc, PantauLaporanState>(
builder: (context, state) {
if (state is PantauLaporanSearchState &&
state.laporan!.length != 0 &&
isPressed == false) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 8,
),
PrimButton(
height: 48,
name: "Bersihkan Hasil Pencarian",
status: FormzStatus.valid,
onPressed: () {
typedSearch = "";
setState(() {
isPressed = true;
isSearching = false;
});
bloc!..add(PantauLaporanSearchEvent(""));
}),
SizedBox(
height: 4,
),
],
),
);
}
return SizedBox(
height: 8,
);
})),

Changes of Search Implementation in My App

So here I will tell you about the changes in the implementation of search in the application that I am working on from time to time.

At first, I implemented a search that will search the results every time there is a change in the search text field. However, this is not recommended in the search that I implemented because the data connected to my application via firebase can continue to change so it is very wasteful to retrieve results every time there is a change in the search text field. To solve this problem, I changed the search implementation by creating a button in the search text field that will return search results when the button is pressed.

However, there are still shortcomings in this implementation because returning the results to their original state, can only be done through refreshing the page or an empty search in the text field. So to overcome these shortcomings, I got the idea to change the button in the search text field when after searching to an “X” button which can return the results as before.

However, this implementation was considered less common by members of my group so I changed it again for the last time by making the “X” button only used to delete the contents of the search text field so that application users can search again. In addition, I added a new button, namely “Bersihkan Hasil Pencarian” after the user searches for something that can be used to return the results as before.

That is what I can tell you about the implementation I did in making searches for the application I created. Hope you like it. Thank you 😄.

--

--