firebase_storage_upload_bloc 1.0.4 copy "firebase_storage_upload_bloc: ^1.0.4" to clipboard
firebase_storage_upload_bloc: ^1.0.4 copied to clipboard

Bloc for resuming uploads to Firebase Storage after app restarts

example/lib/main.dart

import 'dart:async';
import 'dart:convert' show utf8;

import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_storage_mocks/firebase_storage_mocks.dart';
import 'package:firebase_storage_upload_bloc/firebase_storage_upload_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:uuid/uuid.dart';

void main() {
  final mockFirebaseStorage = MockFirebaseStorage();

  runApp(MyApp(firebaseStorage: mockFirebaseStorage));
}

class MyApp extends StatelessWidget {
  const MyApp({required this.firebaseStorage, super.key});

  final FirebaseStorage firebaseStorage;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: .fromSeed(seedColor: Colors.deepPurple),
      ),
      home: RepositoryProvider.value(
        value: firebaseStorage,
        child: BlocProvider<FirebaseStorageUploadBloc>(
          create: (context) => FirebaseStorageUploadBloc(
            firebaseStorage: context.read(),
          ),
          child: const MyHomePage(),
        ),
      ),
    );
  }
}

// Keep in one file to make the example easier to read.
// ignore: prefer-single-widget-per-file
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const .all(12),
        child: Center(
          child: Column(
            mainAxisAlignment: .center,
            children: [
              Expanded(
                child: BlocBuilder<FirebaseStorageUploadBloc, FirebaseStorageUploadState>(
                  builder: (context, state) => ListView.builder(
                    itemCount: state.tasks.length,
                    itemBuilder: (context, index) {
                      final task = state.tasks.keys.elementAtOrNull(index);
                      final description = state.tasks[task];

                      return ListTile(
                        leading: task is UploadTask
                            ? StreamBuilder<TaskSnapshot>(
                                stream: task.snapshotEvents,
                                builder: (context, snapshot) {
                                  if (snapshot.hasError) {
                                    return const Icon(Icons.error_outline);
                                  } else if (snapshot.hasData) {
                                    switch (snapshot.requireData.state) {
                                      case .running:
                                        if (snapshot.requireData.totalBytes > 0) {
                                          return CircularProgressIndicator(
                                            value:
                                                snapshot.requireData.bytesTransferred / snapshot.requireData.totalBytes,
                                          );
                                        }

                                        return const CircularProgressIndicator();

                                      case .success:
                                        return const Icon(Icons.check);

                                      case .paused:
                                        return const Icon(Icons.pause);

                                      case .error:
                                      case .canceled:
                                        return const Icon(Icons.error_outline);
                                    }
                                  } else {
                                    return const CircularProgressIndicator();
                                  }
                                },
                              )
                            : FutureBuilder(
                                future: task,
                                builder: (context, snapshot) {
                                  if (snapshot.connectionState == .done) {
                                    return Icon(snapshot.hasError ? Icons.error_outline : Icons.check);
                                  }

                                  return const CircularProgressIndicator();
                                },
                              ),
                        title: Text(
                          description ?? 'Unknown task',
                          style: Theme.of(context).textTheme.bodyMedium,
                        ),
                      );
                    },
                  ),
                ),
              ),
              TextButton(
                onPressed: () => _uploadFile(context),
                child: const Text('Simulate File Upload'),
              ),
              TextButton(
                onPressed: () => _addTimerTask(context, 'Five second timer task'),
                child: const Text('Add Timer Task'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  static void _uploadFile(BuildContext context) {
    final firebaseStorage = context.read<FirebaseStorage>();
    final bloc = context.read<FirebaseStorageUploadBloc>();

    final id = const Uuid().v4();

    final ref = firebaseStorage.ref().child('$id.txt');
    final data = utf8.encode('Hello world!');

    unawaited(bloc.uploadFile(ref, data, 'text/plain'));

    _addTimerTask(context, 'Mock file uploads instantly, so this task just shows that it happened');
  }

  static void _addTimerTask(BuildContext context, String description) {
    final bloc = context.read<FirebaseStorageUploadBloc>();

    // Timer test task
    final task = Future<void>.delayed(const Duration(seconds: 5));

    bloc.addTask(task, description);
  }
}