Blogging my journey of learning Full stack devlopment and creating an App to get a hands on knowledge.

Search This Blog

Wednesday, 10 May 2023

Thursday, 4 May 2023

Implementing BLOC design pattern for state management of Search Field in Flutter

 Business logic components (BLoC) allow you to separate the business logic from the UI. Using Bloc, we can easily separate the application into multiple layers, first we would have the presentation layer which would contain the UI/Views/widgets, then the business logic layer (Bloc) which will take care about the state changes and will have a dependency on the data access layer which contains a repository class which will act as an abstract class above the data access object classes.

Dependencies:

  • flutter_bloc: ^8.1.2
  • equatable: ^2.0.5
  • http: ^0.13.5

Code Implementation :

Let's build a simple app to implement this Bloc pattern. We will create a simple search field that will search recipe and give the the list of recipe based on your search. I have used Forkify API to search, view the recipe.

data/model/receipe.dart

class Recipe {
Recipe({
required this.publisher,
required this.title,
required this.sourceUrl,
required this.recipeId,
required this.imageUrl,
required this.socialRank,
required this.publisherUrl,
});

String publisher;
String title;
String sourceUrl;
String recipeId;
String imageUrl;
double socialRank;
String publisherUrl;

factory Recipe.fromJson(Map<String, dynamic> json) => Recipe(
publisher: json["publisher"],
title: json["title"],
sourceUrl: json["source_url"],
recipeId: json["recipe_id"],
imageUrl: json["image_url"],
socialRank: json["social_rank"]?.toDouble(),
publisherUrl: json["publisher_url"],
);

Map<String, dynamic> toJson() => {
"publisher": publisher,
"title": title,
"source_url": sourceUrl,
"recipe_id": recipeId,
"image_url": imageUrl,
"social_rank": socialRank,
"publisher_url": publisherUrl,
};
}
data/repositories/search_repositories.dart
import 'package:searchscreen/data/model/pizza.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
abstract class SearchRepository{
Future<List<Recipe>> searchPizza(String query);
}

class SearchRepositoryImpl implements SearchRepository{
List<Recipe> recipes=[];
@override
Future<List<Recipe>> searchPizza(query) async{
try{
var response= await http.get( Uri.parse('https://forkify-api.herokuapp.com/api/search?q=$query' ));
if (response.statusCode == 200) {
var data = json.decode(response.body);
recipes = Pizza
.fromJson(data)
.recipes;
return recipes;
} else
{
return recipes;
}
}
catch(error){
throw (error.toString());

}
}
}
bloc/search_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:searchscreen/bloc/search/search_event.dart';
import 'package:searchscreen/bloc/search/search_state.dart';
import 'package:searchscreen/data/model/pizza.dart';
import 'package:searchscreen/data/repositories/search_repository.dart';
class SearchBloc extends Bloc<SearchEvent,SearchState> {
final SearchRepository repository;

SearchBloc(this.repository) : super(SearchUninitialized()) {
on<Search>(
(event, emit) async {
try {
emit(SearchLoadingState());
final List<Recipe> recipes = await repository.searchPizza(event.query);

emit(SearchLoaded(recipes: recipes));
} catch (e,stackTrace) {
emit(SearchErrorState(message: e.toString()));
}
});
}
}

bloc/search_event.dart

import 'package:equatable/equatable.dart';
abstract class SearchEvent extends Equatable{
const SearchEvent();
}
class Search extends SearchEvent{
late String query;
Search({required this.query});

@override
List<Object> get props => [];
}

bloc/search_state.dart


import 'package:equatable/equatable.dart';
import 'package:searchscreen/data/model/pizza.dart';
abstract class SearchState extends Equatable{

}
class SearchUninitialized extends SearchState{
@override
List<Object?> get props =>[];
}
class SearchLoadingState extends SearchState{

@override
List<Object?> get props => [];

}
class SearchLoaded extends SearchState{
final List<Recipe> recipes;
SearchLoaded({required this.recipes});
@override
List<Object?> get props => [];

}

class SearchErrorState extends SearchState{
final String message;
SearchErrorState({required this.message});
@override
List<Object?> get props => [];

}

main.dart

import 'package:flutter/material.dart';
import 'package:searchscreen/bloc/search/search_bloc.dart';
import 'package:searchscreen/data/repositories/search_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:searchscreen/ui/search_view.dart';
void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return

RepositoryProvider(
create: (context) =>PizzaRepositoryImpl() ,
child: MultiBlocProvider(
providers: [
BlocProvider<PizzaBloc>(
create: (context) => PizzaBloc(PizzaRepositoryImpl()
)..add(FetchPizzaEvent()),lazy: false,),
BlocProvider<SearchBloc>(
create: (context) => SearchBloc(SearchRepositoryImpl()
))
],
child: MaterialApp(
title: 'Recipe Search App',
home: SearchView(),
)),
);

}
}

ui/search_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:searchscreen/bloc/search/search_bloc.dart';
import 'package:searchscreen/bloc/search/search_event.dart';
import 'package:searchscreen/bloc/search/search_state.dart';
import 'package:searchscreen/ui/list.dart';
import 'package:searchscreen/widgets/search_text_field.dart';

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

@override
SearchViewState createState() => SearchViewState();
}

class SearchViewState extends State<SearchView> {


final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_controller.addListener(
() => context
.read<SearchBloc>()
.add(Search(query: _controller.text))
);

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


@override
Widget build(BuildContext context) {

return BlocConsumer<SearchBloc, SearchState>(

listener: (context, state) {
if (state is SearchErrorState) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
const SnackBar(content: Text("Failed to Load")),
);

}


},
builder: (context, state) {
return Scaffold(appBar: AppBar(
title: const Text(
'Recipes'
)
),body:ListView(
shrinkWrap: true,
children: [
Card (child: SearchTextField(
key: const Key('searchPage_searchTextField'),
controller: _controller,
),),
const Divider(),
if(state is SearchLoaded)
buildHintsList(state.recipes)
],
));

},
);
}

}


Output:









Git code : https://github.com/anjutus/searchscreendemo
Read More

Featured Post

Invoice Portal Project Links

Web App link for Invoice Portal  Invoice Portal App lets an user view and create an Invoice. Features: View the list of Number of Invoices b...

Powered by Blogger.

Popular Posts