بحث باسم الموضوع المطلوب

تصدير واستيراد الملفات باستخدام أداة اختيار الملفات في Flutter


{محتويات} 
-تثبيت حزمة اختيار الملف
-تصدير الملفات باستخدام File Picker في Flutter
-تلميحات الامتداد المسموح به
-إضافة رسائل Snackbar
-استيراد الملفات باستخدام File Picker في Flutter
-فرض الامتدادات المسموح بها
-التعامل مع ملحقات الملفات غير المدعومة
-إضافة رسائل Snackbar

تثبيت حزمة اختيار الملف
لتصدير واستيراد الملفات باستخدام أداة اختيار الملفات في Flutter، سنستخدم  حزمة File Picker  https://pub.dev/packages/file_picker . يمكننا تثبيت الحزمة من خلال تنفيذ الأمر التالي داخل مشروعنا:
flutter pub add file_picker
بمجرد تنفيذ الأمر، تأكد من فحص ملفك  pubspec.yaml بحثًا عن التبعيات المضافة. يجب أن ترى حزمة File Picker مضمنة في قسم التبعيات، مثل هذا:
dependencies:
  file_picker: ^8.1.2
تصدير الملفات باستخدام File Picker في Flutter
سنبدأ بتحديث MaterialAppعنصر واجهة المستخدم الخاص بنا لإرجاع FilePickerPageعنصر واجهة مستخدم مخصص. داخل FilePickerPageعنصر واجهة المستخدم، سنضيف أزرارًا لتصدير الملفات واستيرادها. ومع ذلك، في القسم الأول، سنركز على وظيفة التصدير.

الرئيسية دارت
import 'package:flutter/material.dart';
import 'package:save_files_using_file_picker/file_picker_page.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: FilePickerPage(),
    );
  }
}

في الملف أعلاه main.dart، نقوم بإنشاء MyAppعنصر واجهة المستخدم الذي يقوم بإرجاع عنصر واجهة المستخدم الخاص بالتطبيق MaterialApp. داخل MaterialAppعنصر واجهة المستخدم، نقوم بإرجاع عنصر واجهة المستخدم المخصص FilePickerPage.

ملف_منتقي_الصفحة.dart
import 'dart:convert';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';

class FilePickerPage extends StatelessWidget {
  const FilePickerPage({super.key});

  Future<void> _exportFile() async => FilePicker.platform.saveFile(
        bytes: utf8.encode('Only Flutter'),
        fileName: 'only_flutter.txt',
      );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: _exportFile,
              child: const Text('Export File'),
            ),
          ],
        ),
      ),
    );
  }
}

في الملف أعلاه file_picker_page.dart، قمنا بإنشاء FilePickerPageالفئة. هذه الفئة تعيد Rowعنصر واجهة مستخدم مركزي، والذي يحتوي حاليًا على عنصر واجهة مستخدم واحد ElevatedButton. عند الضغط على الزر، _exportFileيتم استدعاء الوظيفة.

داخل _exportFileالدالة، نقوم باستدعاء saveFileالدالة من FilePickerالفئة باستخدام platformgetter الخاصة بها. لاستخدام FilePickerالفئة، نقوم باستيراد حزمة File Picker.

داخل saveFileالدالة، نقوم بتعريف السمتين bytesو fileName. bytesتُستخدم السمة لتعيين محتويات الملف. السمة من النوع Uint8List?، لذلك نستخدمها utf8.encodeلتحويل السلسلة إلى بايتات. fileNameتُستخدم السمة لتعيين اسم الملف المقترح. يمكن للمستخدم دائمًا تغيير اسم الملف.

تلميحات الامتداد المسموح به
تسمح لنا الوظيفة saveFileمن FilePickerالفصل أيضًا بتزويد المستخدم بتلميحات حول امتداد الملف.
Future<void> _exportFile() async => FilePicker.platform.saveFile(
  bytes: utf8.encode('Only Flutter'),
  fileName: 'only_flutter.txt',
  allowedExtensions: ['txt'],
);

في مقتطف التعليمات البرمجية أعلاه، أضفنا allowedExtensionsالمعلمة إلى saveFileالدالة. تقبل هذه المعلمة قائمة من السلاسل، حيث تمثل كل سلسلة امتداد ملف بدون النقطة.

في هذه الحالة، قمنا بتوفير txtالامتداد، وبالتالي .txtسيتم تمييز الملفات التي تحمل امتداد الملف فقط عند استخدام أداة اختيار الملف.

كما لاحظت في لقطة الشاشة، .txtلا يتم تمييز بعض الملفات، وذلك لأنها ملحقة بـ (1). يحدث هذا إذا كنت تريد حفظ ملفات بنفس الاسم.

 إضافة رسائل Snackbar

حتى الآن، قمنا بتنفيذ القدرة على تصدير الملفات، ولكننا لم نقدم بعد أي ملاحظات للمستخدم حول ما إذا كان التصدير ناجحًا أم لا.

import 'dart:convert';


import 'package:file_picker/file_picker.dart';

import 'package:flutter/material.dart';


class FilePickerPage extends StatelessWidget {

  const FilePickerPage({super.key});


  void _showSnackBar(BuildContext context, Color color, String text) {

    ScaffoldMessenger.of(context).showSnackBar(

      SnackBar(

        backgroundColor: color,

        content: Center(child: Text(text)),

        duration: const Duration(seconds: 2),

      ),

    );

  }


  Future<void> _exportFile(BuildContext context) async => FilePicker.platform.saveFile(

    bytes: utf8.encode('Only Flutter'),

    fileName: 'only_flutter.txt',

    allowedExtensions: ['txt'],

  ).then((String? path) {

    if (!context.mounted) return;


    if (path != null) {

      _showSnackBar(context, Colors.green, 'Export successful.');

    } else {

      _showSnackBar(context, Colors.red, 'Export failed.');

    }

  });


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Center(

        child: Row(

          mainAxisAlignment: MainAxisAlignment.spaceEvenly,

          children: [

            ElevatedButton(

              onPressed: () async => _exportFile(context),

              child: const Text('Export File'),

            ),

          ],

        ),

      ),

    );

  }

}


في مقتطف الكود أعلاه، أضفنا _showSnackbarوظيفة لعرض SnackBarالرسائل. ولأننا نريد عرض SnackBarرسائل متعددة، فقد أنشأنا وظيفة لتجنب تكرار الكود.

بعد ذلك، أضفنا thenالدالة لتنفيذ معاودة الاتصال بمجرد اكتمال عملية الإرجاع Futureبواسطة saveFileالدالة. داخل thenالدالة، نتحقق أولاً مما إذا كان السياق مُركَّبًا، ثم نعرض رسالة SnackBar. بناءً على ما إذا كان المسار مُركَّبًا nullأم لا، نعرض إما رسالة حمراء أو خضراء لإظهار ما إذا كان التصدير ناجحًا أم لا.

SnackBarيتم عرض الرسالة الحمراء عند الضغط على زر الرجوع في نظام Android.

استيراد الملفات باستخدام File Picker في Flutter

الآن بعد أن تعرفت على كيفية تصدير الملفات باستخدام أداة اختيار الملفات في Flutter، دعنا ننتقل إلى استيراد الملفات.


 import 'dart:convert';

import 'dart:io';


import 'package:file_picker/file_picker.dart';

import 'package:flutter/material.dart';


class FilePickerPage extends StatelessWidget {

  const FilePickerPage({super.key});


  void _showSnackBar(BuildContext context, Color color, String text) {

    ScaffoldMessenger.of(context).showSnackBar(

      SnackBar(

        backgroundColor: color,

        content: Center(child: Text(text)),

        duration: const Duration(seconds: 2),

      ),

    );

  }


  Future<void> _exportFile(BuildContext context) async => FilePicker.platform.saveFile(

    bytes: utf8.encode('Only Flutter'),

    fileName: 'only_flutter.txt',

  ).then((String? path) {

    if (!context.mounted) return;


    if (path != null) {

      _showSnackBar(context, Colors.green, 'Export successful.');

    } else {

      _showSnackBar(context, Colors.red, 'Export failed.');

    }

  });


  Future<void> _importFile() async =>

      FilePicker.platform.pickFiles().then((FilePickerResult? result) async {

        if (result != null) {

          final PlatformFile selectedFile = result.files.single;


          if (selectedFile.path == null) {

            return;

          }


          final File file = File(selectedFile.path!);


          print(await file.readAsString());

        }

      });


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Center(

        child: Row(

          mainAxisAlignment: MainAxisAlignment.spaceEvenly,

          children: [

            ElevatedButton(

              onPressed: () async => _exportFile(context),

              child: const Text('Export File'),

            ),

            ElevatedButton(

              onPressed: _importFile,

              child: const Text('Import File'),

            ),

          ],

        ),

      ),

    );

  }

}

في مقتطف الكود أعلاه، أضفنا ElevatedButtonعنصر واجهة مستخدم إضافي داخل FilePickerPageعنصر واجهة المستخدم الخاص بنا والذي سيقوم باستدعاء _importFileالوظيفة عند الضغط عليها.

داخل _importFileالدالة، نستدعي pickFilesدالة الفئة FilePickerباستخدام platformgetter. نستخدم thenالدالة أيضًا للتعامل مع النتيجة بمجرد اكتمال Future from pickFiles. داخل هذه الدالة المستدعاة، نحصل على مثيل من FilePickerResult.

النتيجة لها filesخاصية. لتحديد الملف الأول، نستخدم singlegetter ونقوم بتعيينه للمتغير selectedFile. بعد ذلك، نتحقق مما إذا كان مسار الملف المحدد ليس فارغًا. إذا لم يكن فارغًا، نقوم بإنشاء ملف باستخدام Fileفئة Dart من خلال توفير مسار الملف. أخيرًا، نطبع محتويات الملف على الطرفية.

توفر الفئة PlatformFileسمات أكثر من مجرد path. على سبيل المثال، يمكنك أيضًا الوصول إلى name, extension, size, وما إلى ذلك.

فرض الامتدادات المسموح بها

على عكس saveFileوظيفة فئة FilePicker، pickFilesيمكن للوظيفة فرض امتدادات الملفات، بحيث يمكن للمستخدمين فقط تحديد الملفات ذات الامتدادات المحددة. 

Future<void> _importFile() async =>

    FilePicker.platform.pickFiles(

      allowedExtensions: ['txt'],

      type: FileType.custom,

    ).then((FilePickerResult? result) async {

      if (result != null) {

        final PlatformFile selectedFile = result.files.single;


        if (selectedFile.path == null) {

          return;

        }


        final File file = File(selectedFile.path!);


        print(await file.readAsString());

      }

    });


في مقتطف التعليمات البرمجية أعلاه، أضفنا allowedExtensionsالمعلمة إلى pickFilesالدالة. وعلى غرار الدالة saveFile، تأخذ هذه المعلمة أيضًا قائمة من السلاسل، حيث تمثل كل سلسلة امتداد ملف بدون النقطة. عند استخدام المعلمة allowedExtensions، نحتاج أيضًا إلى تعيين typeالمعلمة إلى FileType.custom.

في هذه الحالة، نسمح للمستخدم فقط بتحديد .txtالملفات. وبالتالي، لا يتم تمييز جميع الملفات الأخرى ولا يمكن تحديدها.

التعامل مع ملحقات الملفات غير المدعومة
لا تقبل المعلمة allowedExtensionsسوى امتدادات ملفات معينة. على سبيل المثال، .opmlلا يتم دعم الامتداد. إذا قمنا بتضمين امتدادات غير مدعومة في allowedExtensionsالمعلمة، فسوف يطرح تطبيقنا استثناءً. في مثل هذه الحالات، سنحتاج إلى التعامل مع الامتدادات المسموح بها يدويًا.

Future<void> _importFile() async =>
    FilePicker.platform.pickFiles().then((FilePickerResult? result) async {
      if (result != null) {
        final PlatformFile selectedFile = result.files.single;

        if (selectedFile.path == null) {
          return;
        }

        final allowedExtensions = ['opml'];

        if (!allowedExtensions.any((String extension) =>
            selectedFile.extension?.contains(extension) ?? false)) {
          return;
        }

        final File file = File(selectedFile.path!);

        print(await file.readAsString());
      }
    });

في مقتطف التعليمات البرمجية أعلاه، بدلاً من استخدام المعلمة allowedExtensions، نتحقق مما إذا كان امتداد الملف المحدد موجودًا في allowedExtensionsالقائمة، والتي تتضمن .opmlالامتدادات فقط. نستمر في العمل بالوظيفة فقط إذا كان امتداد الملف مدرجًا في هذه القائمة، وإلا، نخرج من الوظيفة باستخدام إرجاع مبكر.
لسوء الحظ، مع هذا النهج، لا يزال المستخدم قادرًا على تحديد الملفات ذات امتدادات الملفات المختلفة. ومع ذلك، عند الاستخدام الصحيح، لن يتعامل تطبيقك مع بيانات الملف.


إضافة رسائل Snackbar

كما فعلنا مع وظيفة التصدير، من المفيد تقديم ملاحظات للمستخدم عند فشل عملية الاستيراد أو نجاحها. لذلك، يمكننا أيضًا إضافة رسائل SnackBar إلى الوظيفة _importFile. 

import 'dart:convert';

import 'dart:io';


import 'package:file_picker/file_picker.dart';

import 'package:flutter/material.dart';


class FilePickerPage extends StatelessWidget {

  const FilePickerPage({super.key});


  void _showSnackBar(BuildContext context, Color color, String text) {

    ScaffoldMessenger.of(context).showSnackBar(

      SnackBar(

        backgroundColor: color,

        content: Center(child: Text(text)),

        duration: const Duration(seconds: 2),

      ),

    );

  }


  Future<void> _exportFile(BuildContext context) async => FilePicker.platform.saveFile(

    bytes: utf8.encode('Only Flutter'),

    fileName: 'only_flutter.txt',

  ).then((String? path) {

    if (!context.mounted) return;


    if (path != null) {

      _showSnackBar(context, Colors.green, 'Export successful.');

    } else {

      _showSnackBar(context, Colors.red, 'Export failed.');

    }

  });


  Future<void> _importFile(BuildContext context) async =>

      FilePicker.platform.pickFiles(

        allowedExtensions: ['txt'],

        type: FileType.custom,

      ).then((FilePickerResult? result) async {

        if (!context.mounted) return;


        if (result != null) {

          final PlatformFile selectedFile = result.files.single;


          if (selectedFile.path == null) {

            _showSnackBar(context, Colors.red, 'Import failed.');


            return;

          }


          final File file = File(selectedFile.path!);


          await file.readAsString().then((String text) {

            if (!context.mounted) return;


            _showSnackBar(context, Colors.green, text);

          });


          return;

        }


        _showSnackBar(context, Colors.red, 'Import failed.');

      });


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Center(

        child: Row(

          mainAxisAlignment: MainAxisAlignment.spaceEvenly,

          children: [

            ElevatedButton(

              onPressed: () async => _exportFile(context),

              child: const Text('Export File'),

            ),

            ElevatedButton(

              onPressed: () async => _importFile(context),

              child: const Text('Import File'),

            ),

          ],

        ),

      ),

    );

  }

}


في مقتطف التعليمات البرمجية أعلاه، قمنا بإعادة استخدام _showSnackBarالوظيفة داخل _importFileالوظيفة لعرض رسائل SnackBar لكل من عمليات الاستيراد الناجحة والفاشلة. نظرًا لأن الوظيفة _showSnackBarتتطلب السياق، فنحن الآن نمرر السياق إلى الوظيفة _importFileأيضًا.

في هذا المنشور، تعلمت كيفية تصدير واستيراد الملفات باستخدام حزمة File Picker في Flutter. وكما رأيت، فإن الحزمة سهلة الاستخدام وباستخدام الحد الأدنى من التعليمات البرمجية، يمكنك بالفعل تحقيق الكثير. كما ناقشنا خيارات التخصيص مثل تقييد ملحقات الملفات وتقديم الملاحظات للمستخدمين لإبقائهم على اطلاع بحالة عمليات الملفات الخاصة بهم.