background_http_client 0.0.7 copy "background_http_client: ^0.0.7" to clipboard
background_http_client: ^0.0.7 copied to clipboard

A Flutter plugin for background HTTP client operations with Dio-like interface

example/lib/main.dart

import 'dart:async';
import 'dart:io';

import 'package:background_http_client/background_http_client.dart';
import 'package:flutter/material.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(const MyApp());
}

class RequestItem {
  final String id;
  final String path;
  final DateTime registrationDate;
  String? responseFilePath;
  RequestStatus? status;
  Map<String, dynamic>? responseJson;

  RequestItem({
    required this.id,
    required this.path,
    required this.registrationDate,
    this.responseFilePath,
    this.status,
    this.responseJson,
  });
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _client = BackgroundHttpClient();
  final List<RequestItem> _requests = [];
  Timer? _statusTimer;
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _startStatusTimer();
  }

  void _startStatusTimer() {
    _statusTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      if (!mounted) {
        timer.cancel();
        return;
      }

      // Update statuses only for requests that do not have a response yet
      final pendingRequests = _requests.where((r) => r.responseFilePath == null).toList();

      if (pendingRequests.isEmpty) return;

      for (final request in pendingRequests) {
        try {
          final taskInfo = await _client.getRequestStatus(request.id);

          // If the task is not found - skip it
          if (taskInfo == null) {
            continue;
          }

          if (taskInfo.statusEnum == RequestStatus.completed || taskInfo.statusEnum == RequestStatus.failed) {
            final responseTaskInfo = await _client.getResponse(request.id);
            if (responseTaskInfo != null && mounted) {
              setState(() {
                request.status = responseTaskInfo.statusEnum;
                // Extract responseFilePath from responseJson if present
                if (responseTaskInfo.responseJson != null) {
                  final responseFilePath = responseTaskInfo.responseJson!['responseFilePath'] as String?;
                  if (responseFilePath != null && responseFilePath.isNotEmpty) {
                    request.responseFilePath = responseFilePath;
                  }
                }
                request.responseJson = responseTaskInfo.responseJson;
              });
            }
          } else if (mounted) {
            setState(() {
              request.status = taskInfo.statusEnum;
            });
          }
        } catch (e) {
          // Ignore errors when checking status
        }
      }
    });
  }

  Future<void> _createGetRequest() async {
    try {
      final taskInfo = await _client.get('https://httpbin.org/get', queryParameters: {'test': 'value'});
      setState(() {
        _requests.add(
          RequestItem(
            id: taskInfo.id,
            path: taskInfo.path,
            registrationDate: taskInfo.registrationDateTime,
            status: taskInfo.statusEnum,
          ),
        );
      });
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } catch (e) {
      // Error handling
    }
  }

  Future<void> _createGetRequestWithCustomId() async {
    try {
      final customId = 'my-custom-get-request-${DateTime.now().millisecondsSinceEpoch}';
      final taskInfo = await _client.get(
        'https://httpbin.org/get',
        queryParameters: {'customId': customId},
        requestId: customId,
      );
      setState(() {
        _requests.add(
          RequestItem(
            id: taskInfo.id,
            path: taskInfo.path,
            registrationDate: taskInfo.registrationDateTime,
            status: taskInfo.statusEnum,
          ),
        );
      });
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } catch (e) {
      // Error handling
    }
  }

  Future<void> _createPostRequest() async {
    try {
      final taskInfo = await _client.post(
        'https://httpbin.org/post',
        data: {'message': 'Hello from background_http_client'},
        headers: {'Content-Type': 'application/json'},
      );
      setState(() {
        _requests.add(
          RequestItem(
            id: taskInfo.id,
            path: taskInfo.path,
            registrationDate: taskInfo.registrationDateTime,
            status: taskInfo.statusEnum,
          ),
        );
      });
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } catch (e) {
      // Error handling
    }
  }

  Future<void> _createMultipartRequest() async {
    try {
      final tempDir = await getTemporaryDirectory();
      final testFile = File('${tempDir.path}/test_file.txt');
      await testFile.writeAsString('This is a test file for multipart request\nCreated at: ${DateTime.now()}');

      if (!await testFile.exists()) {
        return;
      }

      final taskInfo = await _client.postMultipart(
        'https://httpbin.org/post',
        fields: {'description': 'Test file', 'category': 'example'},
        files: {'file': MultipartFile(filePath: testFile.path, filename: 'test_file.txt', contentType: 'text/plain')},
        requestId: 'multipart-request-${DateTime.now().millisecondsSinceEpoch}',
      );
      setState(() {
        _requests.add(
          RequestItem(
            id: taskInfo.id,
            path: taskInfo.path,
            registrationDate: taskInfo.registrationDateTime,
            status: taskInfo.statusEnum,
          ),
        );
      });
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } catch (e) {
      // Error handling
    }
  }

  Future<void> _createLargeFileDownload() async {
    try {
      final customId = 'large-file-download-${DateTime.now().millisecondsSinceEpoch}';
      final taskInfo = await _client.get('https://httpbin.org/bytes/500000', requestId: customId);
      setState(() {
        _requests.add(
          RequestItem(
            id: taskInfo.id,
            path: taskInfo.path,
            registrationDate: taskInfo.registrationDateTime,
            status: taskInfo.statusEnum,
          ),
        );
      });
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } catch (e) {
      // Error handling
    }
  }

  void _clearRequests() {
    setState(() {
      _requests.clear();
    });
  }

  Future<void> _openFile(String filePath) async {
    try {
      await OpenFile.open(filePath);
    } catch (e) {
      // Error handling
    }
  }

  String _getStatusText(RequestStatus? status) {
    if (status == null) return 'Unknown';
    return switch (status) {
      RequestStatus.inProgress => 'In progress',
      RequestStatus.completed => 'Completed',
      RequestStatus.failed => 'Error',
    };
  }

  Color _getStatusColor(RequestStatus? status) {
    if (status == null) return Colors.grey;
    return switch (status) {
      RequestStatus.inProgress => Colors.blue,
      RequestStatus.completed => Colors.green,
      RequestStatus.failed => Colors.red,
    };
  }

  @override
  void dispose() {
    _statusTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Background HTTP Client Example'),
          actions: [IconButton(icon: const Icon(Icons.clear), onPressed: _clearRequests, tooltip: 'Clear list')],
        ),
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  Row(
                    children: [
                      Expanded(
                        child: ElevatedButton(onPressed: _createGetRequest, child: const Text('GET')),
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: ElevatedButton(
                          onPressed: _createGetRequestWithCustomId,
                          child: const Text('GET (custom ID)'),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      Expanded(
                        child: ElevatedButton(onPressed: _createPostRequest, child: const Text('POST')),
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: ElevatedButton(onPressed: _createMultipartRequest, child: const Text('Multipart')),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: _createLargeFileDownload,
                      child: const Text('Download large file (streaming)'),
                    ),
                  ),
                ],
              ),
            ),
            const Divider(),
            Expanded(
              child: _requests.isEmpty
                  ? const Center(child: Text('Request list is empty'))
                  : ListView.builder(
                      controller: _scrollController,
                      padding: const EdgeInsets.all(16.0),
                      itemCount: _requests.length,
                      itemBuilder: (context, index) {
                        final request = _requests[index];
                        return Card(
                          child: ListTile(
                            title: Text('ID: ${request.id}', style: const TextStyle(fontSize: 12)),
                            subtitle: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const SizedBox(height: 4),
                                Text(
                                  'Registered: ${request.registrationDate.toString().substring(0, 19)}',
                                  style: const TextStyle(fontSize: 10, color: Colors.grey),
                                ),
                                const SizedBox(height: 4),
                                TextButton(
                                  onPressed: () => _openFile(request.path),
                                  child: Text(
                                    'Request: ${request.path.split('/').last}',
                                    style: const TextStyle(fontSize: 12),
                                  ),
                                ),
                                if (request.responseFilePath != null) ...[
                                  const SizedBox(height: 4),
                                  TextButton(
                                    onPressed: () => _openFile(request.responseFilePath!),
                                    child: Text(
                                      'Response: ${request.responseFilePath?.split('/').last}',
                                      style: const TextStyle(fontSize: 12),
                                    ),
                                  ),
                                ],
                                const SizedBox(height: 4),
                                Row(
                                  children: [
                                    Container(
                                      width: 8,
                                      height: 8,
                                      decoration: BoxDecoration(
                                        color: _getStatusColor(request.status),
                                        shape: BoxShape.circle,
                                      ),
                                    ),
                                    const SizedBox(width: 4),
                                    Text(
                                      _getStatusText(request.status),
                                      style: TextStyle(
                                        fontSize: 12,
                                        color: _getStatusColor(request.status),
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                  ],
                                ),
                              ],
                            ),
                          ),
                        );
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
159
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for background HTTP client operations with Dio-like interface

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on background_http_client

Packages that implement background_http_client