برنامه نویسی

آپلود آفلاین فایل در فلاتر

Summarize this content to 400 words in Persian Lang

مشکل

قبلاً ذخیره‌سازی تصویر یکی از ویژگی‌های کم مصرف برنامه ما بود و فقط کپی‌های دیجیتال کوچک را ذخیره می‌کرد تا امضایی که اغلب کمتر از 10 کیلوبایت بود، اخیراً مشتریانی داشتیم که می‌خواستند در هر سفارش عکس بگیرند و حتی چندین عکس بگیرند.

گزینه ذخیره سازی فعلی ما یک مشکل ایجاد کرد، زیرا کاربران Firebase به دلیل قابلیت های بیدرنگ آن و پشتیبانی آفلاین، ما به سادگی تصاویر خود را در یک مجموعه اختصاصی به عنوان رشته base64 ذخیره می کردیم، زمانی که فایل ها کمتر از 10 کیلوبایت هستند و همیشه فقط 1 مورد وجود دارد. آنها، این یک مشکل نیست، با این حال Firebase دارای محدودیت اندازه سند 1024 کیلوبایت است.

هر کسی که از یک تلفن مدرن استفاده می کند می تواند مشکل را در اینجا ببیند، حتی گرفتن عکس با کیفیت 20٪ همچنان باعث می شود که بسیاری از کاربران بتوانند با موفقیت 0 عکس را ذخیره کنند. هر دو دارنده آیفون 15 در تیم این مشکل را داشتند.

راه حل (قسمت اول)

هنگامی که شروع به حل این مشکل کردیم، از فضای ذخیره سازی Firebase استفاده کردیم، زیرا دارای سطح عملکرد آفلاین و خودکار رزومه بود، اما برای اطمینان از اینکه از آن راضی هستیم، به معنای واقعی کلمه برای حدود 4 ماه به عنوان یک گزینه ذخیره سازی اضافی در کنار Firestore آزمایش کردیم. ، به این ترتیب با فرض اینکه همه چیز خوب پیش رفت، مشتریانی که از تلفن های جدیدتر استفاده می کنند همچنان به تصاویر دسترسی خواهند داشت، البته از طریق یک کانال پشتیبانی.

این به خوبی کار کرد، یا بنابراین فکر می‌کردیم که در آزمایش همه چیز عالی کار می‌کند، چه دستگاه روشن یا آفلاین باشد، بنابراین ادامه دادیم و شروع به به‌روزرسانی بقیه سیستم کردیم تا استفاده از Firestore برای دسترسی به فایل‌ها متوقف شود.

توپ منحنی

بلافاصله پس از شروع این فرآیند، یکی از مشتریان ما مشکلاتی را در مورد آپلود این فایل گزارش کرد، چیزی که ما واقعاً هرگز نمی‌توانستیم آن را آزمایش کنیم، و صادقانه بگویم هرگز به آن فکر نمی‌کردیم، آزمایش در شرایط زباله بود، در حالی که Firebase Storage با اینترنت کارآمد و بدون کارکرد خوب کار می‌کرد. اینترنت، زمانی که اینترنت سطل زباله بود یک کابوس بود، چیزی که ما متوجه نشده بودیم این است که تا زمانی که یک اتصال وجود داشت منتظر می ماند و به طور مداوم برای شروع آپلود، قبل از اجازه دادن به هر شکل واقعی، صبر می کرد و دوباره تلاش می کرد. پشتیبانی آفلاین/رزومه

این بدان معنی است که بسیاری از درایورها برای مدت زمان قابل توجهی به مشتریان گیر کرده اند، برخی گزارش می دهند که تا 30 دقیقه به سادگی سعی در آپلود تصاویر کوچک دارند، به فضای ذخیره سازی که بسیاری از آنها حدود 500 کیلوبایت است نگاه می کنند، در شرایط عادی مشکلی نیست، اما زمانی که اینترنت وجود دارد یک کابوس است. زباله کامل

برگشت به تابلوی طراحی

ما تصمیم گرفتیم راه حل را دوباره کار کنیم تا ابتدا کاملاً آفلاین باشد و از رزومه برنامه ریزی شده و اجرای ایزوله پشتیبانی کنیم.

برای انجام این کار، من با اضافه کردن Drift و Workmanager به پروژه شروع کردم، Drift یک گزینه بسیار خوب و آسان برای استفاده از پایگاه داده محلی و Workmanager یک برنامه پس‌زمینه، مانند CRON است.

فرآیند بسیار ساده بود، به جای ارسال مستقیم آپلودهای معلق به فضای ذخیره سازی Firebase، ابتدا آن را به Drift نوشتیم و تمام داده های مربوطه را به همراه Uint8List (BlobColumn در رانش) داده های تصویری.

جریان کار آپلود واقعی بر اساس منطق اتصال خروجی مشروط شد که هم متکی به اتصال دستگاه و هم پینگ موفقیت آمیز به Google برای تأیید اتصال کارآمد است.

در حال آپلود

هنگامی که همه چیز را در DB داشتیم و در موقعیتی قرار گرفتیم که آپلود را شروع کنیم، از یکی از قابلیت های DB Isolates ساخته شده در Drift استفاده کردیم، بنابراین یک نمونه از DB را به یک Isolated تبدیل کنید و به جای ایزوله خود آپلود را در داخل آن شروع کنید. یا در رشته UI.

در اینجا ما آن را داریم uploadFiles روشی که از Drifts استفاده می کند computeWithDatabse این روش معمولاً برای رسیدگی به پرس و جوهای محاسباتی که می توانند رابط کاربری را قفل کنند استفاده می شود، اما برای رسیدگی به کارهای پس زمینه و برنامه ریزی شده نیز به همان اندازه کار می کند.

@pragma(‘vm:entry-point’)
Future<void> uploadFiles(AppDatabase database) async {
final token = RootIsolateToken.instance!;

await database.computeWithDatabase(
computation: (database) async {
BackgroundIsolateBinaryMessenger.ensureInitialized(token);

await databaseFileUpload(database);

return Future.value(true);
},
connect: (connection) => AppDatabase(connection),
);
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا 2 روش وجود دارد، computation که پردازش تراکنش های DB شما را انجام می دهد و connect که به طور طبیعی اتصال به پایگاه داده شما را انجام می دهد.

شما می توانید اسناد Drifts را برای راه اندازی دنبال کنید، اما تغییر کوچکی که برای تسهیل این امر باید انجام شود این است که پایگاه داده شما به صورت اختیاری باید یک QueryExecutor

AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این به شما امکان می دهد به جای اینکه اتصال خود را باز کنید، از یک اتصال موجود عبور کنید _openConnection تابعی که در بالا خواهید دید.

این به‌صورت آنلاین عالی کار می‌کرد، اما به دلایلی که من هیچ وقت برای درک آن وقت نداشتم، بارگذاری Firebase Storage کاری را انجام می‌داد که به سادگی ایزوله‌ها را شکست، بنابراین ما هرگز نتوانستیم تصاویر را با موفقیت به‌صورت آفلاین یا از طریق زمان‌بندی آپلود کنیم.

همانطور که برای Workmanager اسناد آنها تنظیمات و استفاده را به خوبی شرح می دهد تا از فرآیند پس زمینه ای که من آن را فراخوانی می کنم پشتیبانی کند uploadFiles تابع بالا و در جایی که یک نمونه از AppDatabase وارد شده است، برای بقیه برنامه‌ها ثبت نام ساده‌تر است AppDatabase به عنوان تک قلو در get_it مطابق با الف Riverpod ارائه‌دهنده درون برنامه، در مورد باز کردن چندین نمونه هشدار می‌دادم و در حالی که احتمالاً راه درستی برای انجام این کار با Riverpod وجود دارد، زیرا Drift خود نمونه‌هایی دارد که از آن استفاده می‌کنند، مشکل زمان…

پلان سی؟؟

خوشبختانه من به اندازه کافی NodeJS/NestJS می‌دانم که در BE خطرناک و تا حدی مفید باشد، بنابراین صبح شنبه قبل از ساعت 5 صبح، چون نمی‌توانستم این باگ و چالش واقعاً سرگرم‌کننده را کنار بگذارم، یک نقطه پایانی بسیار خالی را در BE خود صرف کردم تا همه چیز را آزمایش کنم. این کار به سادگی از داده ها خارج شد.

چند آزمایش سریع از طریق Postman برای اطمینان از کارآمد بودن آن، دوباره به برنامه رفتم و Firebase Storage را بیرون کشیدم و نقطه پایانی REST و چند تغییر کد کوچک را که به صورت آنلاین، آفلاین و در پس‌زمینه آپلود می‌کردم وصل کردم.

رفتن به Seapoint برای دویدن با سگ‌ها… (نه من این شنبه خاص را تعطیل نکردم)

بنابراین در حالی که به نظر می‌رسد Firebase Storage از اجرای در پس‌زمینه Isolate بسیار ناراضی است، http بسیار خوشحال بود که متعهد شد.

جریان HTTP برای پشتیبانی از گردش‌های کاری پس‌زمینه بسیار ساده است، اما نکاتی وجود دارد.

@pragma(‘vm:entry-point’)
Future<String?> _backgroundFileUpload(
UploadRequestData data,
Uint8List imageBuffer,
) async {
final sharedPreferences = await SharedPreferences.getInstance()
..reload();

final bearerToken = sharedPreferences.getString(BEARER_TOKEN_KEY)!;
final url = sharedPreferences.getString(API_URL)!;

final headers = {
“content-type”: “application/json”,
“Authorization”: “Bearer $bearerToken”,
“x-app-version”: APP_VERSION,
};

final uri = Uri.https(url, ‘/api/upload/pod’);

final request = http.MultipartRequest(‘POST’, uri);

request.headers.addAll(headers);

request.files.add(
http.MultipartFile.fromBytes(
‘image’, // The key name as expected by the API
Uint8List.fromList(imageBuffer),
contentType: MediaType(‘image’, ‘jpeg’),
filename: data.file_name,
),
);

request.fields.addAll({
‘task_id’: data.task_id,
‘file_name’: data.file_name,
‘type’: data.type,
‘status’: data.status,
‘timestamp’: data.timestamp,
});

final response = await request.send();

if (response.statusCode != 201) {
return null;
}

return response.stream.bytesToString();
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مانند اکثر برنامه‌ها، ما از متغیرهای محیطی برای API‌های مختلفی که باید به آن‌ها متصل شویم (Dev/Staging/Prod) استفاده می‌کنیم. SharedPreferences یکی از ساده‌ترین راه‌ها برای وارد کردن این متغیرها به یک Isolate است، ذخیره شدن در یک فایل روی دستگاه شما به سادگی باید مطمئن شوید که قبل از دسترسی به هر داده‌ای دوباره بارگیری می‌کنید تا مطمئن شوید نه تنها داده‌ها را دارید، بلکه آخرین نسخه آن را نیز در اختیار دارید. که جدا می کند.

از آنجا، همه چیز همانطور که برای خیر کار می کند MultipartFile آپلود کنید، در حالی که ما در حال ذخیره سازی هستیم Uint8List در BlobColumn در Drift متوجه شدم که انتقال آن به API منجر به خطا می شود و باید آن را تبدیل می کنم BlobColumn به یک Uint8List دوباره

بسته بندی

از اینجا به بعد واقعاً فقط پاکسازی و چند آزمایش دیگر انجام شد، چند مشکل در کد حل شد که فقط در نسخه انتشار نشان داده شد، اطمینان حاصل کرد که API واقعاً فایل‌ها را ذخیره کرده است و تمام اسناد DB مربوطه در Firebase و فرآیند پاکسازی برای آپلودهای موفق در داخل برنامه انجام شد.

مشکل

قبلاً ذخیره‌سازی تصویر یکی از ویژگی‌های کم مصرف برنامه ما بود و فقط کپی‌های دیجیتال کوچک را ذخیره می‌کرد تا امضایی که اغلب کمتر از 10 کیلوبایت بود، اخیراً مشتریانی داشتیم که می‌خواستند در هر سفارش عکس بگیرند و حتی چندین عکس بگیرند.

گزینه ذخیره سازی فعلی ما یک مشکل ایجاد کرد، زیرا کاربران Firebase به دلیل قابلیت های بیدرنگ آن و پشتیبانی آفلاین، ما به سادگی تصاویر خود را در یک مجموعه اختصاصی به عنوان رشته base64 ذخیره می کردیم، زمانی که فایل ها کمتر از 10 کیلوبایت هستند و همیشه فقط 1 مورد وجود دارد. آنها، این یک مشکل نیست، با این حال Firebase دارای محدودیت اندازه سند 1024 کیلوبایت است.

هر کسی که از یک تلفن مدرن استفاده می کند می تواند مشکل را در اینجا ببیند، حتی گرفتن عکس با کیفیت 20٪ همچنان باعث می شود که بسیاری از کاربران بتوانند با موفقیت 0 عکس را ذخیره کنند. هر دو دارنده آیفون 15 در تیم این مشکل را داشتند.

راه حل (قسمت اول)

هنگامی که شروع به حل این مشکل کردیم، از فضای ذخیره سازی Firebase استفاده کردیم، زیرا دارای سطح عملکرد آفلاین و خودکار رزومه بود، اما برای اطمینان از اینکه از آن راضی هستیم، به معنای واقعی کلمه برای حدود 4 ماه به عنوان یک گزینه ذخیره سازی اضافی در کنار Firestore آزمایش کردیم. ، به این ترتیب با فرض اینکه همه چیز خوب پیش رفت، مشتریانی که از تلفن های جدیدتر استفاده می کنند همچنان به تصاویر دسترسی خواهند داشت، البته از طریق یک کانال پشتیبانی.

این به خوبی کار کرد، یا بنابراین فکر می‌کردیم که در آزمایش همه چیز عالی کار می‌کند، چه دستگاه روشن یا آفلاین باشد، بنابراین ادامه دادیم و شروع به به‌روزرسانی بقیه سیستم کردیم تا استفاده از Firestore برای دسترسی به فایل‌ها متوقف شود.

توپ منحنی

بلافاصله پس از شروع این فرآیند، یکی از مشتریان ما مشکلاتی را در مورد آپلود این فایل گزارش کرد، چیزی که ما واقعاً هرگز نمی‌توانستیم آن را آزمایش کنیم، و صادقانه بگویم هرگز به آن فکر نمی‌کردیم، آزمایش در شرایط زباله بود، در حالی که Firebase Storage با اینترنت کارآمد و بدون کارکرد خوب کار می‌کرد. اینترنت، زمانی که اینترنت سطل زباله بود یک کابوس بود، چیزی که ما متوجه نشده بودیم این است که تا زمانی که یک اتصال وجود داشت منتظر می ماند و به طور مداوم برای شروع آپلود، قبل از اجازه دادن به هر شکل واقعی، صبر می کرد و دوباره تلاش می کرد. پشتیبانی آفلاین/رزومه

این بدان معنی است که بسیاری از درایورها برای مدت زمان قابل توجهی به مشتریان گیر کرده اند، برخی گزارش می دهند که تا 30 دقیقه به سادگی سعی در آپلود تصاویر کوچک دارند، به فضای ذخیره سازی که بسیاری از آنها حدود 500 کیلوبایت است نگاه می کنند، در شرایط عادی مشکلی نیست، اما زمانی که اینترنت وجود دارد یک کابوس است. زباله کامل

برگشت به تابلوی طراحی

ما تصمیم گرفتیم راه حل را دوباره کار کنیم تا ابتدا کاملاً آفلاین باشد و از رزومه برنامه ریزی شده و اجرای ایزوله پشتیبانی کنیم.

برای انجام این کار، من با اضافه کردن Drift و Workmanager به پروژه شروع کردم، Drift یک گزینه بسیار خوب و آسان برای استفاده از پایگاه داده محلی و Workmanager یک برنامه پس‌زمینه، مانند CRON است.

فرآیند بسیار ساده بود، به جای ارسال مستقیم آپلودهای معلق به فضای ذخیره سازی Firebase، ابتدا آن را به Drift نوشتیم و تمام داده های مربوطه را به همراه Uint8List (BlobColumn در رانش) داده های تصویری.

جریان کار آپلود واقعی بر اساس منطق اتصال خروجی مشروط شد که هم متکی به اتصال دستگاه و هم پینگ موفقیت آمیز به Google برای تأیید اتصال کارآمد است.

در حال آپلود

هنگامی که همه چیز را در DB داشتیم و در موقعیتی قرار گرفتیم که آپلود را شروع کنیم، از یکی از قابلیت های DB Isolates ساخته شده در Drift استفاده کردیم، بنابراین یک نمونه از DB را به یک Isolated تبدیل کنید و به جای ایزوله خود آپلود را در داخل آن شروع کنید. یا در رشته UI.

در اینجا ما آن را داریم uploadFiles روشی که از Drifts استفاده می کند computeWithDatabse این روش معمولاً برای رسیدگی به پرس و جوهای محاسباتی که می توانند رابط کاربری را قفل کنند استفاده می شود، اما برای رسیدگی به کارهای پس زمینه و برنامه ریزی شده نیز به همان اندازه کار می کند.

@pragma('vm:entry-point')
Future<void> uploadFiles(AppDatabase database) async {
  final token = RootIsolateToken.instance!;

  await database.computeWithDatabase(
    computation: (database) async {
      BackgroundIsolateBinaryMessenger.ensureInitialized(token);

      await databaseFileUpload(database);

      return Future.value(true);
    },
    connect: (connection) => AppDatabase(connection),
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا 2 روش وجود دارد، computation که پردازش تراکنش های DB شما را انجام می دهد و connect که به طور طبیعی اتصال به پایگاه داده شما را انجام می دهد.

شما می توانید اسناد Drifts را برای راه اندازی دنبال کنید، اما تغییر کوچکی که برای تسهیل این امر باید انجام شود این است که پایگاه داده شما به صورت اختیاری باید یک QueryExecutor

AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این به شما امکان می دهد به جای اینکه اتصال خود را باز کنید، از یک اتصال موجود عبور کنید _openConnection تابعی که در بالا خواهید دید.

این به‌صورت آنلاین عالی کار می‌کرد، اما به دلایلی که من هیچ وقت برای درک آن وقت نداشتم، بارگذاری Firebase Storage کاری را انجام می‌داد که به سادگی ایزوله‌ها را شکست، بنابراین ما هرگز نتوانستیم تصاویر را با موفقیت به‌صورت آفلاین یا از طریق زمان‌بندی آپلود کنیم.

همانطور که برای Workmanager اسناد آنها تنظیمات و استفاده را به خوبی شرح می دهد تا از فرآیند پس زمینه ای که من آن را فراخوانی می کنم پشتیبانی کند uploadFiles تابع بالا و در جایی که یک نمونه از AppDatabase وارد شده است، برای بقیه برنامه‌ها ثبت نام ساده‌تر است AppDatabase به عنوان تک قلو در get_it مطابق با الف Riverpod ارائه‌دهنده درون برنامه، در مورد باز کردن چندین نمونه هشدار می‌دادم و در حالی که احتمالاً راه درستی برای انجام این کار با Riverpod وجود دارد، زیرا Drift خود نمونه‌هایی دارد که از آن استفاده می‌کنند، مشکل زمان…

پلان سی؟؟

خوشبختانه من به اندازه کافی NodeJS/NestJS می‌دانم که در BE خطرناک و تا حدی مفید باشد، بنابراین صبح شنبه قبل از ساعت 5 صبح، چون نمی‌توانستم این باگ و چالش واقعاً سرگرم‌کننده را کنار بگذارم، یک نقطه پایانی بسیار خالی را در BE خود صرف کردم تا همه چیز را آزمایش کنم. این کار به سادگی از داده ها خارج شد.

چند آزمایش سریع از طریق Postman برای اطمینان از کارآمد بودن آن، دوباره به برنامه رفتم و Firebase Storage را بیرون کشیدم و نقطه پایانی REST و چند تغییر کد کوچک را که به صورت آنلاین، آفلاین و در پس‌زمینه آپلود می‌کردم وصل کردم.


رفتن به Seapoint برای دویدن با سگ‌ها… (نه من این شنبه خاص را تعطیل نکردم)


بنابراین در حالی که به نظر می‌رسد Firebase Storage از اجرای در پس‌زمینه Isolate بسیار ناراضی است، http بسیار خوشحال بود که متعهد شد.

جریان HTTP برای پشتیبانی از گردش‌های کاری پس‌زمینه بسیار ساده است، اما نکاتی وجود دارد.

@pragma('vm:entry-point')
Future<String?> _backgroundFileUpload(
  UploadRequestData data,
  Uint8List imageBuffer,
) async {
  final sharedPreferences = await SharedPreferences.getInstance()
    ..reload();

  final bearerToken = sharedPreferences.getString(BEARER_TOKEN_KEY)!;
  final url = sharedPreferences.getString(API_URL)!;

  final headers = {
    "content-type": "application/json",
    "Authorization": "Bearer $bearerToken",
    "x-app-version": APP_VERSION,
  };

  final uri = Uri.https(url, '/api/upload/pod');

  final request = http.MultipartRequest('POST', uri);

  request.headers.addAll(headers);

  request.files.add(
    http.MultipartFile.fromBytes(
      'image', // The key name as expected by the API
      Uint8List.fromList(imageBuffer),
      contentType: MediaType('image', 'jpeg'),
      filename: data.file_name,
    ),
  );

  request.fields.addAll({
    'task_id': data.task_id,
    'file_name': data.file_name,
    'type': data.type,
    'status': data.status,
    'timestamp': data.timestamp,
  });

  final response = await request.send();

  if (response.statusCode != 201) {
    return null;
  }

  return response.stream.bytesToString();
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مانند اکثر برنامه‌ها، ما از متغیرهای محیطی برای API‌های مختلفی که باید به آن‌ها متصل شویم (Dev/Staging/Prod) استفاده می‌کنیم. SharedPreferences یکی از ساده‌ترین راه‌ها برای وارد کردن این متغیرها به یک Isolate است، ذخیره شدن در یک فایل روی دستگاه شما به سادگی باید مطمئن شوید که قبل از دسترسی به هر داده‌ای دوباره بارگیری می‌کنید تا مطمئن شوید نه تنها داده‌ها را دارید، بلکه آخرین نسخه آن را نیز در اختیار دارید. که جدا می کند.

از آنجا، همه چیز همانطور که برای خیر کار می کند MultipartFile آپلود کنید، در حالی که ما در حال ذخیره سازی هستیم Uint8List در BlobColumn در Drift متوجه شدم که انتقال آن به API منجر به خطا می شود و باید آن را تبدیل می کنم BlobColumn به یک Uint8List دوباره

بسته بندی

از اینجا به بعد واقعاً فقط پاکسازی و چند آزمایش دیگر انجام شد، چند مشکل در کد حل شد که فقط در نسخه انتشار نشان داده شد، اطمینان حاصل کرد که API واقعاً فایل‌ها را ذخیره کرده است و تمام اسناد DB مربوطه در Firebase و فرآیند پاکسازی برای آپلودهای موفق در داخل برنامه انجام شد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا