Handle API Lists with Flutter BLoC

Build dynamic Flutter apps by mastering API data fetching.

 · 3 min read

Absolutely! Here's a breakdown of how to implement fetching a list from an API in Flutter using the BLoC pattern:


Project Setup

1. Dependencies:

Make sure you have these packages in your pubspec.yaml file:

dependencies:
 flutter_bloc: ^8.1.1 # Or the latest version
 http: ^0.13.5 # Or the latest version
 equatable: ^2.0.5 # For easier state comparisons

2. Project Structure: Create a basic structure like this:

lib/
 bloc/  
   my_api_bloc.dart
   my_api_event.dart
   my_api_state.dart
 models/          
   my_data_model.dart  
 repository/      
   my_api_repository.dart
 main.dart
 ui/
   my_data_list_view.dart


Steps

1. Data Model (my_data_model.dart) Define a model class to represent the list item fetched from your API:

class MyDataModel {
 final int id;
 final String name;
 // Other properties

 MyDataModel({required this.id, required this.name, /* Other properties */});

 factory MyDataModel.fromJson(Map<String, dynamic> json) {
   return MyDataModel(
     id: json['id'],
     name: json['name']
     // ...other properties
   );
 }
}

2. API Repository (my_api_repository.dart) Create a class to handle the network request:

import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/my_data_model.dart';

class MyApiRepository {
 Future<List<MyDataModel>> fetchDataList() async {
   final response = await http.get(Uri.parse('https://your-api-endpoint'));

   if (response.statusCode == 200) {
     final parsed = jsonDecode(response.body).cast<Map<String, dynamic>>();
     return parsed.map<MyDataModel>((json) => MyDataModel.fromJson(json)).toList();
   } else {
     throw Exception('Failed to load data');
   }
 }
}

3. BLoC (my_api_bloc.dart)

  1. Events (my_api_event.dart)

Define events to trigger API calls:

abstract class MyApiEvent {}

class FetchDataList extends MyApiEvent {}
  1. States (my_api_state.dart)

Define states to represent the UI state:

abstract class MyApiState extends Equatable {}

class MyApiInitial extends MyApiState {
 @overrideList<Object> get props => [];
}

class MyApiLoading extends MyApiState {
 @overrideList<Object> get props => [];
}

class MyApiLoaded extends MyApiState {
 final List<MyDataModel> dataList;

 MyApiLoaded(this.dataList);

 @overrideList<Object> get props => [dataList];
}

class MyApiError extends MyApiState {
 final String message;

 MyApiError(this.message);

 @overrideList<Object> get props => [message];
}
  1. The BLoC

Handle the events and emit states:

import 'package:flutter_bloc/flutter_bloc.dart';
import '../repository/my_api_repository.dart';
// ... other importsclass MyApiBloc extends Bloc<MyApiEvent, MyApiState> {
 final MyApiRepository _repository;

 MyApiBloc(this._repository) : super(MyApiInitial()) {
   on<FetchDataList>((event, emit) async {
     emit(MyApiLoading());
     try {
       final dataList = await _repository.fetchDataList();
       emit(MyApiLoaded(dataList));
     } catch (e) {
       emit(MyApiError(e.toString()));
     }
   });
 }
}


4. UI (my_data_list_view.dart)

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/my_api_bloc.dart';

class MyDataListPage extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: Text('Data List')),
     body: BlocProvider(
       create: (_) => MyApiBloc()..add(FetchDataList()), // Fetch data on initialization
       child: BlocBuilder<MyApiBloc, MyApiState>(
         builder: (context, state) {
           if (state is MyApiInitial) {
             return Center(child: Text('Initial state'));
           } else if (state is MyApiLoading) {
             return Center(child: CircularProgressIndicator());
           } else if (state is MyApiLoaded) {
             return ListView.builder(
               itemCount: state.dataList.length,
               itemBuilder: (context, index) {
                 final data = state.dataList[index];
                 return ListTile(
                   title: Text(data.name),
                   subtitle: Text('ID: ${data.id}'),
                 );
               },
             );
           } else if (state is MyApiError) {
             return Center(child: Text('Error: ${state.message}'));
           } else {
             return Container(); // Unreachable state
           }
         },
       ),
     ),
   );
 }
}

Explanation

  1. BlocProvider: Provides your MyApiBloc instance to the UI tree.
  2. BlocBuilder: Reactively rebuilds a portion of the UI based on your BLoC's state.
  3. State Handling:MyApiInitial: Display a placeholder if needed.
  4. MyApiLoading: Show a loading indicator.
  5. MyApiLoaded: Build the list using a ListView.builder.
  6. MyApiError: Display an error message.

Key Points

  1. The ..add(FetchDataList()) in the BlocProvider will trigger the API call when the UI initializes.
  2. Customize the way you display your data (the ListTile is just a basic example).
  3. Consider how you want to handle refreshing the data (you might add a refresh button or pull-to-refresh functionality).

No comments yet.

Add a comment
Ctrl+Enter to add comment