openfoodfacts / smooth-app

🤳🥫 The new Open Food Facts mobile application for Android and iOS, crafted with Flutter and Dart
https://world.openfoodfacts.org/open-food-facts-mobile-app?utm_source=off&utf_medium=web&utm_campaign=github-repo
Apache License 2.0
834 stars 279 forks source link

Uploading photos on server takes too much time #2192

Closed teolemon closed 2 years ago

teolemon commented 2 years ago

What

I've retested New product addition with a video. It's really long, especially on 4G. https://photos.app.goo.gl/w6VcqSpvyeqqp8z48

monsieurtanuki commented 2 years ago

@teolemon I wouldn't blame the 4G. But:

  1. obviously you were testing in the worst conditions - full size pictures - where I would expect "real people" to crop
  2. we may reduce the size of the image (before upload) without the user even noticing it. In another project it was even something I had to do, because whatsapp reduced the size of the image anyway and destroyed my well designed drawings. At least make it the default (e.g. reduce to 1000 pixel max) and explaining why, with an option to upload the full size.
  3. the circular animation is boring - we should have a tomato-apple-banana-carrot dance animation.
  4. the text too is boring - I guess something like "thank you, we're adding this picture to our database" would be more positive.
  5. I don't think we would be able to display a linear progress here, but perhaps starting with a ping request would help us make it more alive.
  6. would that be possible to upload in background? not sure about the UX, and there's probably already at least one issue for that.
AshAman999 commented 2 years ago

would that be possible to upload in the background? not sure about the UX, and there's probably already at least one issue with that.

I am doing this, we can easily force the upload to be in the background but then it's about how to notify the user when the work manager finishes its task of uploading Here's the apk, can I hear a ux suggestion on this How I retry in case of failure is like this 

switch (counter) {
        case 0:
          duration = const Duration(seconds: 30);
          break;
        case 1:
          duration = const Duration(minutes: 1);
          break;
        case 2:
          duration = const Duration(minutes: 30);
          break;
        case 3:
          duration = const Duration(hours: 1);
          break;
        case 4:
          duration = const Duration(hours: 6);
          break;
        default:
          duration = const Duration(days: 1);
          break;
      } 

https://drive.google.com/file/d/1-UaDQtCi3mSwBN0WtvRQUBg39kR3OqIg/view?usp=sharing

stephanegigandet commented 2 years ago

how to notify the user when the work manager finishes its task of uploading

My personal opinion on that is that we should not notify the user. I don't see the point, users take photos, they assume they will be sent. If we really want to make this information available, then it would be more in the user's profile: # of photos sent + # of photos waiting to be sent.

monsieurtanuki commented 2 years ago

If the question is just "how to notify the user", I assume we can do this with a provider. I would tend to agree with @stephanegigandet regarding UX.

To me, the biggest challenges here are:

AshAman999 commented 2 years ago

@monsieurtanuki Trying with something like this, seemed to work real good, also had the apk here https://drive.google.com/file/d/1-UaDQtCi3mSwBN0WtvRQUBg39kR3OqIg/view?usp=sharing

the background process (isolate? just async?) that uploads the pictures and refreshes the product locally (that's where you would notify with your provider)

Was trying to do something with workanager ,added benefits would be the background scheduling and there's the retry method to try to upload multiple times(ios doesn't support retry by default)

@pragma(
    'vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void callbackDispatcher() {
  Workmanager().executeTask(
    (String task, Map<String, dynamic>? inputData) async {
      // make a counter with task as key as it is unique for each task
      final int counter = inputData!['counter'] as int;
      // if task is greate than 4 , that means it has been executed 5 times
      if (counter > 5) {
        // returns true to let platform know that the task is completed
        return Future<bool>.value(true);
      }
      Duration duration;
      switch (counter) {
        case 0:
          duration = const Duration(seconds: 30);
          break;
        case 1:
          duration = const Duration(minutes: 1);
          break;
        case 2:
          duration = const Duration(minutes: 30);
          break;
        case 3:
          duration = const Duration(hours: 1);
          break;
        case 4:
          duration = const Duration(hours: 6);
          break;
        default:
          duration = const Duration(days: 1);
          break;
      }
      bool shouldRetry = false;
      try {
        final SendImage image = SendImage(
          lang: ProductQuery.getLanguage(),
          barcode: inputData['barcode'].toString(),
          imageField:
              ImageFieldExtension.getType(inputData['imageField'].toString()),
          imageUri: Uri.parse(inputData['imageUri'].toString()),
        );
        final Status result = await OpenFoodAPIClient.addProductImage(
          ProductQuery.getUser(),
          image,
        );
        shouldRetry = result.error != null || result.status != 'status ok';
      } catch (e) {
        shouldRetry = true;
        debugPrint(e.toString());
      }
      if (shouldRetry) {
        inputData['counter'] = counter + 1;
        Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
        Workmanager().registerOneOffTask(
          task,
          'ImageUploadWorker',
          // constraints: Constraints(
          //   networkType: NetworkType.connected,
          // ),
          inputData: inputData,
          initialDelay: duration,
        );
        return Future<bool>.error('Failed and it will try again');
      } else {
        return Future<bool>.value(true);
      }
    },
  );
}

// To invoke 
  final Map<String, dynamic> inputData = <String, dynamic>{
    'barcode': barcode,
    'imageField': imageField.value,
    'imageUri': File(imageUri.path).path,
    'counter': 0,
  };
  await Workmanager().initialize(
      callbackDispatcher, // The top level function, aka callbackDispatcher
      isInDebugMode:
          true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
      );
  final String uniqueId = 'ImageUploader_${barcode}_${imageField.value}';
  await Workmanager().registerOneOffTask(
    uniqueId, 'ImageUploadWorker',
    inputData: inputData,
  );

refreshes the product locally (that's where you would notify your provider)

Seems like I can't access the context or any dart plugins, nor evensharedprefwhen using workmanager, so how exactly to notify or do use provide here is being a bit tricky to me, currently what I was thinking is not show a snackbar to the user that the theupload has been queued it will be uploaded soon

monsieurtanuki commented 2 years ago

@AshAman999 I'm not familiar with workmanager or isolate, just familiar enough to have noticed that there are limitations ;) That said, I think @g123k developed isolate for the barcode scan: you may find interesting ideas in his code.

Having a look at the isolate docs, you may find the answers you're looking for:

All Dart code runs in an isolate, and code can access classes and values only from the same isolate. Different isolates can communicate by sending values through ports (see ReceivePort, SendPort).

I'm not sure isolate is the technical solution for neither cases (barcode scan and file upload). A video about async/isolate: https://www.youtube.com/watch?v=5AxWC49ZMzs.

Well, if it was my project I would try at least running async upload methods without await. Which probably wouldn't help alerting the main app when it's done. Just an idea: it's probable that with isolate or with async methods you can open the database, so you can write in the database when you're done, frequently check the database to see if something has successfully been uploaded, and react accordingly. Not a very subtile notification, but that would probably work.

Good luck!

monsieurtanuki commented 2 years ago

Additional interesting video here, about: Capture d’écran 2022-06-25 à 18 52 02