skip to Main Content

Hi Everyone I wanted to show video uploading progress bar continuously like 0,1,2………100%
here is code which I implemented but it didn’t work as I want

I want to show progress bar continuously 0,1,2,………100%

Concise – To implement a video uploading progress bar that increments continuously from 0% to 100%, along with handling two API calls, you can follow this approach:

Requirements:
Progress Bar Behavior:
The progress bar should increment continuously (from 0% to 100%) while both APIs are working.
It should move steadily until the video upload is done, then move to 100% when the second API (training data) is called and completed.
Two APIs:
API 1: Upload the video file.
API 2: Upload training data once the video file is successfully uploade

Controller code - 

import 'package:get/get.dart';
import 'package:interkashi/modules/components/extension/get_extension.dart';
import 'package:interkashi/modules/feature/academy/model/training_video_upload_model.dart';
import 'package:interkashi/modules/feature/academy/service/upload_video_service.dart';

class UploadVideoController extends GetxController {
  final UploadVideoService _uploadVideoService;
  UploadVideoController(this._uploadVideoService);

  Rxn<String> selectedVideoPath = Rxn<String>();
  RxBool isUploading = RxBool(false);
  RxDouble uploadProgress = RxDouble(0.0);
  Rx<bool?> uploadSuccess = Rx<bool?>(null);
  final double maxSizeMB = 100;
  RxBool flag = true.obs;
  late String lessonId;
  late String courseId;

  Timer? progressTimer;

  @override
  void onInit() {
    super.onInit();
    final arguments = Get.arguments;
    lessonId = arguments['lessonId'];
    courseId = arguments['courseId'];
  }

  @override
  void onClose() {
    progressTimer?.cancel();
    super.onClose();
  }

  Future<void> checkFileSize(String? video) async {
    if (video == null) return;

    File videoFile = File(video);
    int fileSize = videoFile.lengthSync();
    double mxSize = fileSize / (1024 * 1024);
    if (mxSize > maxSizeMB) {
      flag.value = false;
      GetExt.snackBar("Please Upload less than 100MB file");
    } else {
      selectedVideoPath.value = video;
      flag.value = true;
    }
  }

  void startProgressBar() {
    // Start moving the progress bar slowly until it reaches 40%.
    progressTimer = Timer.periodic(Duration(milliseconds: 300), (timer) {
      if (uploadProgress.value < 0.4) {
        uploadProgress.value += 0.01; // Slow increment until 40%
      } else {
        timer.cancel(); // Stop increment at 40%, wait for first API
      }
    });
  }

  Future<void> uploadVideo() async {
    if (selectedVideoPath.value == null || !flag.value) {
      return;
    }

    uploadSuccess.value = null;
    isUploading.value = true;
    uploadProgress.value = 0.0;

    startProgressBar(); // Start initial progress bar

    // First API: Upload the video file
    final uploadResponse = await _uploadVideoService.uploadVideo(
      selectedVideoPath.value!,
      (progress) {
        // Progress for the first API (video upload) but don't let it exceed 70%
        if (progress <= 0.4) {
          uploadProgress.value = progress * 0.4; // Increment up to 40%
        }
      },
    );

    if (uploadResponse != null) {
      // First API succeeded, jump progress to 70%
      uploadProgress.value = 0.7;

      // Second API: Upload the training data
      final trainingResponse = await _uploadVideoService.uploadTrainingData(
        TrainingVideoUploadModel(
          lessonId: lessonId,
          courseId: courseId,
          videoUrl: uploadResponse.video.url,
          thumbnailUrl: uploadResponse.thumbnail.url,
        ),
      );

      if (trainingResponse != null) {
        // Gradually increase progress to 100% as the second API completes
        while (uploadProgress.value < 1.0) {
          await Future.delayed(Duration(milliseconds: 100)); // Small delay for smooth progress
          uploadProgress.value += 0.03; // Gradually increase from 70% to 100%
        }

        uploadProgress.value = 1.0; 
        await Future.delayed(Duration(milliseconds: 500)); 
        uploadSuccess.value = true;
      } else {
        // Second API failed
        uploadSuccess.value = false;
        GetExt.snackBar("Failed to upload training data");
      }
    } else {
      // First API failed
      uploadSuccess.value = false;
      GetExt.snackBar("Failed to upload video");
    }

    selectedVideoPath.value = null;
    isUploading.value = false;
    progressTimer?.cancel(); 
  }
}
Provider - 
import 'dart:io';
import 'package:get/get_connect/connect.dart';
import 'package:http/http.dart' as http;
import 'package:interkashi/modules/components/provider/base_provider.dart';
import 'package:interkashi/modules/feature/academy/model/training_video_upload_model.dart';

class UploadVideoProvider extends BaseProvider {
  Future<http.Response> uploadVideo(File file) async {
    return await uploadFile("/files/videoAndThumbnailUpload", file);
  }

  Future<Response> uploadTrainingData(TrainingVideoUploadModel trainingVideoModel) async {
    String url = "/training/upload";

    final body = {
      'lessonId': trainingVideoModel.lessonId,
      'courseId': trainingVideoModel.courseId,
      'videoUrl': trainingVideoModel.videoUrl,
      'thumbnailUrl': trainingVideoModel.thumbnailUrl,
    };
    final response = await post(url, body);
    return response;
  }
}
Service - 
import 'dart:convert';
import 'dart:io';

import 'package:interkashi/modules/components/service/base_service.dart';
import 'package:interkashi/modules/feature/academy/model/training_video_upload_model.dart';
import 'package:interkashi/modules/feature/academy/model/upload_video_model.dart';
import 'package:interkashi/modules/feature/academy/provider/upload_video_provider.dart';

class UploadVideoService extends BaseService {
  final UploadVideoProvider _uploadVideoProvider;

  UploadVideoService(this._uploadVideoProvider);

  Future<UploadVideoModel?> uploadVideo(String filePath,Function(double) onProgress) async {
    try {
      final response = await _uploadVideoProvider.uploadVideo(File(filePath));

      return UploadVideoModel.fromJson(jsonDecode(response.body));
    } catch (e) {
      print("Error during video upload: $e");
      return null;
    }
  }

  Future<TrainingVideoUploadModel?> uploadTrainingData(TrainingVideoUploadModel trainingVideoModel) async {
    try {
      final response = await _uploadVideoProvider.uploadTrainingData(trainingVideoModel);
      if (response.statusCode == 200) {
        if (response.body is Map<String, dynamic>) {
          return TrainingVideoUploadModel.fromJson(response.body);
        } else {
          return TrainingVideoUploadModel.fromJson(jsonDecode(response.body));
        }
      } else {
        return null;
      }
    } catch (e) {
      print('Error during training data upload: $e');
      return null;
    }
  }
}
Page Code - 
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:interkashi/modules/components/buttons/buttons.dart';
import 'package:interkashi/modules/components/extension/get_extension.dart';
import 'package:interkashi/modules/components/theme/kashi_theme.dart';
import 'package:interkashi/modules/feature/academy/controller/upload_video_controller.dart';
import 'package:interkashi/modules/feature/academy/widget/upload_done_widget.dart';
import 'package:interkashi/modules/feature/academy/widget/upload_failed_widget.dart';
import 'package:interkashi/modules/routes/kashi_routes.dart';
import 'package:lottie/lottie.dart';

class VideoUploadingProgressPage extends StatefulWidget {
  @override
  State<VideoUploadingProgressPage> createState() => _VideoUploadingProgressPageState();
}

class _VideoUploadingProgressPageState extends State<VideoUploadingProgressPage> with TickerProviderStateMixin {
  final UploadVideoController _controller = Get.find<UploadVideoController>();

  late AnimationController _animationController;
  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 10),
    )..repeat(reverse: false, period: Duration(seconds: 10));
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() {
        if (_controller.uploadSuccess.value != null) {
          if (_controller.uploadSuccess.value!) {
            return UploadDoneWidget();
          } else {
            return UploadFailedWidget();
          }
        }
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Lottie.asset(
              'assets/lottie/uploading.json',
              controller: _animationController,
              animate: true,
              repeat: false,
              reverse: false,
              width: MediaQuery.sizeOf(context).height * 0.3,
              fit: BoxFit.cover,
            ),
            SizedBox(height: 60),
            Obx(() {
              final progressPercentage = (_controller.uploadProgress.value * 100).toInt();
              return Text(
                '$progressPercentage%',
                style: KashiTheme.sh2SemiBold.copyWith(
                  color: KashiColors.whiteFA,
                ),
              );
            }),
            SizedBox(height: 8),
            Padding(
              padding: EdgeInsets.only(left: 20, right: 17),
              child: Container(
                width: double.infinity,
                decoration: BoxDecoration(
                  color: KashiColors.grey3,
                  borderRadius: BorderRadius.circular(6),
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(6),
                  child: Obx(() {
                    return LinearProgressIndicator(
                      value: _controller.uploadProgress.value,
                      backgroundColor: Colors.transparent,
                      color: KashiColors.green2,
                      minHeight: 12,
                    );
                  }),
                ),
              ),
            ),
            SizedBox(height: 15),
            Text(
              'Uploading your Training...',
              style: KashiTheme.h3SemiBold
                  .copyWith(
                    fontWeight: FontWeight.w500,
                    color: KashiColors.whiteFA,
                  )
                  .toDMSanFontsFamily(),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 16),
            Text(
              'Please wait while we upload your training video.',
              style: KashiTheme.sh2SemiBold
                  .copyWith(
                    fontWeight: FontWeight.w500,
                    color: KashiColors.grey6,
                  )
                  .toDMSanFontsFamily(),
              textAlign: TextAlign.center,
            ),
          ],
        );
      }),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.only(left: 20, right: 20, bottom: 16),
        child: Obx(() {
          final isUploadComplete =
              _controller.uploadProgress.value == 1.0 && (_controller.uploadSuccess.value ?? false);
          final isUploadFailed = (_controller.uploadSuccess.value == false);
          final buttonText = (_controller.uploadSuccess.value ?? true) ? "Done" : "Try Upload Again";
          final buttonAction = (isUploadComplete || isUploadFailed)
              ? () {
                  if (_controller.uploadSuccess.value ?? false) {
                    GetExt.snackBar('Video uploaded successfully');
                    Get.until((route) => route.settings.name == KashiRoutes.home);
                  } else {
                    Get.until((route) => route.settings.name == KashiRoutes.uploadVideoPage);
                  }
                }
              : null;
          final buttonBackgroundColor =
              (isUploadComplete || isUploadFailed) ? KashiColors.primaryColor : KashiColors.primary2;
          final buttonTextColor = (isUploadComplete || isUploadFailed) ? KashiColors.whiteFA : KashiColors.primary04;

          return Buttons.primary(
            text: buttonText,
            onPressed: buttonAction,
            backgroundColor: buttonBackgroundColor,
            textColor: buttonTextColor,
            size: ButtonSize.md,
            margin: EdgeInsets.zero,
          );
        }),
      ),
    );
  }

2

Answers


  1. Chosen as BEST ANSWER

    I got answer

     static Future<String> fileUpload({required File file, OnUploadProgressCallback? onUploadProgress}) async {
    //assert(file != null);
    
    //debugger();
    
    final url = '$baseUrl/api/file';
    
    final fileStream = file.openRead();
    
    int totalByteLength = file.lengthSync();
    
    final httpClient = getHttpClient();
    
    final request = await httpClient.postUrl(Uri.parse(url));
    
    request.headers.set(HttpHeaders.contentTypeHeader, ContentType.binary);
    
    request.headers.add("filename", file_util.basename(file.path));
    
    request.contentLength = totalByteLength;
    
    int byteCount = 0;
    Stream<List<int>> streamUpload = fileStream.transform(
      StreamTransformer.fromHandlers(
        handleData: (data, sink) {
          byteCount += data.length;
    
          if (onUploadProgress != null) {
            onUploadProgress(byteCount, totalByteLength);
            // CALL STATUS CALLBACK;
          }
    
          sink.add(data);
        },
        handleError: (error, stack, sink) {
          debugPrint(error.toString());
        },
        handleDone: (sink) {
          sink.close();
          // UPLOAD DONE;
        },
      ),
    );
    
    await request.addStream(streamUpload);
    
    final httpResponse = await request.close();
    
    if (httpResponse.statusCode != 200) {
      throw Exception('Error uploading file');
    } else {
      return await readResponseAsString(httpResponse);
    }
    

    }

    Its working fine

    https://github.com/salk52/Flutter-File-Upload-Download/blob/master/upload_download_app/lib/services/file_service.dart


  2. Your implementation is time based instead of real progress based, so that is always going to look strange.
    I suggest structuring you progress bar differently, as the sum of the progress in the two underlying activities (API calls). Define your progress p = 0.7 * a + 0.3 * b where a is the progress of API 1 and b that of API 2, both going from 0 to 1. You show a progress bar based on p and ideally your progress a and b is based on real upload progress (i.e. a = bytes uploaded / file size for the file upload API).

    This way, both operations contribute to the overall progress, and the whole thing looks smooth and realistic. Note the values 0.7 and 0.3 simply represent what proportion of overall progress is provided by a and b so you can change as you see fit, as long as they add to 1.0

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search