Block Pattern

Real-Time Example: Live Post Search with switchMap and JSONPlaceholder

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, or eum 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

Leave a Reply

Your email address will not be published. Required fields are marked *