Real-Time Example: Live Post Search with switchMap
and JSONPlaceholder
๐ก Goal:
- Use
TextField
to accept a search query. - Fetch and filter posts from JSONPlaceholder.
- Show results matching the search query.
- Use
switchMap
to cancel previous search requests.
๐ฆ Step 1: Dependencies
Add these to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
http: ^0.13.6
rxdart: ^0.27.7
๐ง Step 2: Create the SearchBloc
import 'dart:convert';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
class Post {
final int id;
final String title;
final String body;
Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) => Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
class SearchBloc {
final _querySubject = BehaviorSubject<String>();
Function(String) get onQueryChanged => _querySubject.sink.add;
Stream<List<Post>> get posts => _querySubject
.debounceTime(Duration(milliseconds: 300))
.distinct()
.switchMap((query) => _fetchAndFilterPosts(query));
Stream<List<Post>> _fetchAndFilterPosts(String query) async* {
final response =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
if (response.statusCode == 200) {
final List decoded = json.decode(response.body);
final posts = decoded.map((e) => Post.fromJson(e)).toList();
final filtered = posts
.where((post) =>
post.title.toLowerCase().contains(query.toLowerCase()) ||
post.body.toLowerCase().contains(query.toLowerCase()))
.toList();
yield filtered;
} else {
yield [];
}
}
void dispose() {
_querySubject.close();
}
}
๐ผ๏ธ Step 3: UI Code (Flutter Widget)
import 'package:flutter/material.dart';
import 'search_bloc.dart'; // your BLoC file
class PostSearchPage extends StatefulWidget {
@override
_PostSearchPageState createState() => _PostSearchPageState();
}
class _PostSearchPageState extends State<PostSearchPage> {
final _bloc = SearchBloc();
@override
void dispose() {
_bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Search Posts")),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Enter keyword...',
border: OutlineInputBorder()),
onChanged: _bloc.onQueryChanged,
),
SizedBox(height: 16),
Expanded(
child: StreamBuilder<List<Post>>(
stream: _bloc.posts,
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(child: Text('Start typing...'));
final posts = snapshot.data!;
if (posts.isEmpty) return Center(child: Text('No results found.'));
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return ListTile(
title: Text(post.title),
subtitle: Text(post.body, maxLines: 2, overflow: TextOverflow.ellipsis),
);
},
);
},
),
),
],
),
),
);
}
}
๐งช Test It
- Type
sunt
,qui
, oreum
to see relevant posts. - Type quickly, and
switchMap
ensures only the latest input triggers a request. - Previous ongoing HTTP calls are effectively discarded.
โ Conclusion
Using switchMap
with real-time input and a real API gives your Flutter app:
- Better performance
- Cleaner logic
- User-friendly experience