เนื้อหานี้จะมาดูเกี่ยวกับการนำไฟล์จากภายนอก มาใช้
งานใน app หรือการ import ไฟล์เข้ามาใน app เพื่อใช้งาน
เป็นการเลือกไฟล์ข้อมูลที่อยู่ในเครื่องหรือจากที่จัดเก็บภาย
นอกเช่น SD card ซึ่งเราอาจจะดาวน์โหลดมาไว้ หรือนำเข้ามา
จากวิธีทางอื่น เนื้อหาตอนนี้ใช้โค้ดต่อเนื่องจากตอนที่แล้ว ทบทวน
ได้ที่บทความด้านล่าง
ประยุกต์สร้าง TextEditor อย่างง่าย ใน Flutter http://niik.in/1069
https://www.ninenik.com/content.php?arti_id=1069 via @ninenik
*เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/1069
การ import ไฟล์จากภายนอก เราจะใช้ package ที่ชื่อว่า File Picker ซึ่งเป็นตัวที่
เรียกใช้งานรูปแบบวิธีการเรียกดูไฟล์ของระบบที่มีอยู่แล้วมาใช้อีกที โดยสามารถกำหนดให้เราสามารถ
เลือกไฟล์ทั้งแบบเลือกไฟล์เดียว หรือหลายไฟล์พร้อมกัน หรือแบบมีกำหนดชนิดของไฟล์ที่อนุญาต
ตัว package นี้ก็สามารถทำได้ สามารถดูความสามารถทั้งหมดที่หน้าเพจ package ซึ่งในที่นี้จะแนะนำ
เฉพาะส่วนที่จำเป็นเบื้องต้นเท่านั้น
การติดตั้ง File Picker
กำหนดใน package dependencies ที่ชื่อ File Picker เข้าไปในไฟล์ pubspec.yaml ดังนี้
dependencies: file_picker: ^8.1.2
จากนั้นทำการ import มาใช้งานในหน้าที่ต้องการ
import 'package:file_picker/file_picker.dart';
การทำงานของ File Picker
เมื่อเราทำการเรียกใช้งานคำสั่งเพื่อ import ไฟล์ผ่าน file picker ก็จะทำการเปิด app ของระบบที่จัดการเกี่ยว
กับไฟล์ต่างๆ ในเครื่องที่เราสามารถเลือกโฟลเดอร์หรือแหล่งข้อมูลที่จะดูข้อมูลไฟล์ ซึ่งปกติถ้าเข้าใช้งานครั้งแรก
ก็จะถามขอสิทธิ์การเข้าถึงไฟล์และโฟลเดอร์ก่อน ให้เราอนุญาต เมื่อเราเลือกไฟล์ที่ต้องการแล้ว ตัว app จัดการ
ไฟล์นั้นก็จะปิดไป เราก็จะกลับมายัง app ของเรา ไฟล์ที่ถูกเลือกจะถูก copy ไปยังโฟลเดอร์ cache/file_picker
ให้อัตโนมัติ เราก็จะได้ข้อมูลของไฟล์ที่ import สำหรับไปจัดการต่อ แต่ถ้าสมมติเราเปิด app ที่จะเลือกไฟล์แล้ว
แต่เปลี่ยนใจยกเลิกการเลือกไฟล์ app จัดการไฟล์ก็จะปิดตัวไป และส่งค่า null กลับมา เราก็สามารถกำหนด
การทำงานกรณีเป็น null ได้ตามต้องการ รูปแบบนี้คือหลักการทำงานเบื้องต้น
จะเห็นว่ารูปแบบการทำงานข้างต้นถึงจะเป็นการ import ไฟล์เข้ามาใน app แต่ก็ยังอยู้ใน cache ที่สามารถถูก
ลบออกไปตอนไหนก็ได้ ดังนั้นเราต้องกำหนดคำสั่งการทำงานต่อถ้าต้องการนำไฟล์ที่ import มา ไปไว้ในโฟลเดอร์
ของ app ซึ่งสำหรับ flutter ก็จะใช้เป็นโฟลเดอร์ app_flutter ดูรุปโครงสร้างโฟลเดอร์ในระบบ ทบทวนด้านล่าง
ในรูปตัวอย่างเราเพิ่มไอคอนสำหรับ import ไฟล์เข้ามาเป็นตัวแรก ถ้ากดที่ไอคอน ก็จะไปทำการเรียกใช้งานคำสัง
import ไฟล์ ดูตัวอย่างคำสั่ง import ไฟล์ ที่ใช้งาน file_picker เบื้องต้น
// คำสั่ง import ไฟล์ผ่าน file_picker void _importFile() async { try{ // FilePickerResult? result = await FilePicker.platform.pickFiles(); FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true); if (result != null) { // File _file = File(result.files.single.path!); List<File> _file = result.paths.map((path) => File(path!)).toList(); print(_file); } else { print("User canceled the picker"); } }catch(e){ print(e); } }
ในตัวอย่างด้านบน ได้ทำการคอมเม้นท์ปิดส่วนของการเลือกทีละไฟล์ไว้ กรณีที่ใช้เป็นแบบไฟล์เดียว ตัวแปร
_file จะคืนค่าเป็นข้อมูลไฟล์เดียวมาให้ ถ้ามีการ import และเลือกแบบหลายไฟล์ได้ ก็จะคืนค่าเป็น List<File>
ซึ่งอาจจะมีหลายไฟล์หรือไฟล์เดียวก็ได้ ขึ้นกับว่าจะเลือกกี่ไฟล์ ในกรณีที่ result เป็น null หรือยกเลิกการเลือก
ไฟล์ ในตัวอย่างเราแค่ print ข้อความแจ้งเท่านั้น แต่ถ้ามีการประยุกต์อย่างอื่น ก็สามารถกำหนดคำสั่งการทำงาน
เพิ่มเติมส่วนนี้ได้
มาลองดูคำสั่งการทำงานเบื้องต้น คือเราจะเลือก 2 ไฟล์เพื่อดูผลลัพธ์
จะเห็นว่าเมื่อเลือกไฟล์เสร็จแล้วก็กลับมายังหน้า app ปกติเหมือนไม่มีอะไรขึ้น แต่พอเราเข้าไปในโฟลเดอร์ที่
ชื่อ cache / file_picker ตามตัวอย่าง ก็จะเห็นไฟล์ที่เราทำการ import เข้ามา ไฟล์ที่ตำแหน่งนี้ ถ้าแค่นำไปแสดง
ชั่วคราว เช่น นำรูปเข้ามาและแสดงใน widget โดยยังไม่มีการบันทึกไว้ในระบบ เราก็อาจจะใช้ path ใน ตำแหน่ง
cache แสดงก่อนก็ได้
มาดูตัวอย่างการกำหนดการตั้งค่าเพิ่มเติม
FilePickerResult? result = await FilePicker.platform.pickFiles( allowMultiple: true, type: FileType.custom, allowedExtensions: ['jpg', 'pdf', 'doc'], );
ผลที่ได้ก็จะแสดงเฉพาะไฟล์ที่มีนามสกุลตามที่กำหนดเท่านั้นให้เราเลือก เป็นการกรองไฟล์ที่จะ import ให้สามารถ
เลือกได้สะดวกมากขึ้น
เราสามารถดูรายละเอียดของไฟล์เพิ่มเติมได้ ด้วยคำสั่งดังนี้
if (result != null) { // File _file = File(result.files.single.path!); List<File> _file = result.paths.map((path) => File(path!)).toList(); result.files.forEach((file) { // ดูรายละเอียดข้อมูลของไฟล์ print(file.name); print(file.bytes); print(file.size); print(file.extension); print(file.path); }); // print(_file); } else { print("User canceled the picker"); }
ต่อไปเราจะประยุกต์โดยการใช้งาน คือ จากปกติ เมื่อ import เข้ามาแล้วจะอยู่ในส่วน cache เราก็จะ
ทำการ copy มาไว้ในโฟลเดอร์ app ที่กำลังใช้งาน
// คำสั่ง import ไฟล์ผ่าน file_picker void _importFile() async { try{ // FilePickerResult? result = await FilePicker.platform.pickFiles(); FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true); if (result != null) { // File _file = File(result.files.single.path!); List<File> _file = result.paths.map((path) => File(path!)).toList(); result.files.forEach((file) async { // วนลุป copy ไฟล์ไปยังโฟลเดอร์ที่ใช้งาน // กำหนด path ของไฟล์ใหม่ แล้วทำการ copy จาก cache ไปไว้ใน app String newPath = "${_currentFolder!.path}/${file.name}"; var cachefile = File(file.path!); // กำหนด file object var newFile = await cachefile.copy(newPath); // ข้อมูลไฟล์ print(file.name); print(file.bytes); print(file.size); print(file.extension); print(file.path); // ตัวสุดท้ายทำงานเสร็จ if(file == result.files.last){ // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } }); } else { print("User canceled the picker"); } }catch(e){ print(e); } }
ดูตัวอย่างผลลัพธ์การทำงาน
เริ่มต้นเราเข้าไปยังโฟลเดอร์ของข้อมูล app ที่เราจะเก็บไฟล์ที่ import จากนั้นทำการเลือกไฟล์
ทั้งหมด 4 ไฟล์ หลังจากเลือกไฟล์ ไฟล์ทั้งหมดจะถูกนำไปเข้ามาใน cahce และเราทำคำสั่ง copy
มาไว้ในส่วนของโฟลเดอร์ที่เรากำลังใช้งานอยู่ รายการไฟล์ที่ import เข้ามาก็จะแสดงดังรูป
ไฟล์ explorer.dart
import 'dart:io'; import 'dart:convert'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:file_picker/file_picker.dart'; class Explorer extends StatefulWidget { static const routeName = '/explorer'; const Explorer({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _ExplorerState(); } } class _ExplorerState extends State<Explorer> { List<FileSystemEntity?>? _folders; String _currentPath = ''; // เก็บ path ปัจจุบัน Directory? _currentFolder; // เก็บ โฟลเดอร์ที่กำลังใช้งาน // ตัวแปรเก็บ index รายการที่เลือก List<int> _selectedItems = []; // สร้างฟอร์ม key หรือ id ของฟอร์มสำหรับอ้างอิง final _formKey = GlobalKey<FormState>(); // กำหนดตัวแปรรับค่า final _textData = TextEditingController(); @override void initState() { // TODO: implement initState super.initState(); _loadFolder(); } void _loadFolder() async { // ข้อมูลเกี่ยวกับโฟลเดอร์ Directory ต่างๆ final tempDirectory = await getTemporaryDirectory(); final appSupportDirectory = await getApplicationSupportDirectory(); final appDocumentsDirectory = await getApplicationDocumentsDirectory(); final externalDocumentsDirectory = await getExternalStorageDirectory(); final externalStorageDirectories = await getExternalStorageDirectories(type: StorageDirectory.music); final externalCacheDirectories = await getExternalCacheDirectories(); /* print(tempDirectory); print(appSupportDirectory); print(appDocumentsDirectory); print(externalDocumentsDirectory); print(externalCacheDirectories); print(externalStorageDirectories); */ // เมื่อโหลดขึ้นมา เาจะเปิดโฟลเดอร์ของ package เป้นโฟลเดอร์หลัก _currentFolder = appDocumentsDirectory.parent; _currentPath = appDocumentsDirectory.parent.path; final myDir = Directory(_currentPath); setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); } @override void dispose() { _textData.dispose(); super.dispose(); } // เปิดโฟลเดอร์ และแสดงรายการในโฟลเดอร์ void _setPath(dir) async { _currentFolder = dir; _currentPath = dir.path; final myDir = Directory(_currentPath); try{ setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); }catch(e){ print(e); } _selectedItems.clear(); // ล้างค่าการเลือกทั้งหมด } // คำสังลบไฟล์ void _deleteFile(path) async { final deletefile = File(path); // กำหนด file object final isExits = await deletefile.exists(); // เช็คว่ามีไฟล์หรือไม่ if(isExits){ // ถ้ามีไฟล์ try{ await deletefile.delete(); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งลบโฟลเดอร์ void _deleteFolder(path) async { final deleteFolder = Directory(path); // สร้าง directory object var isExits = await deleteFolder.exists(); // เช็คว่ามีแล้วหรือไม่ if(isExits){ // ถ้ามีโฟลเดอร์ try{ await deleteFolder.delete(recursive: true); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // ลบข้อมูลที่เลือกทั้งหมด void _deleteAll() async { bool _confirm; // สร้างตัวแปรรับค่า ยืนยันการลบ _confirm = await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete selected item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE") ), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); if(_confirm){ // ถ้ายืนยันการลบ เป็น true try{ // วนลูป index แล้วอ้างอึงข้อมูลไฟล์ จากนั้นใช้คำสั่ง delete() แบบรองรับการลบข้อมูลด้านในถ้ามี // ในกรณีเป็นโฟลเดอร์ _selectedItems.forEach((index) async { await _folders![index]!.delete(recursive: true); }); }catch(e){ print(e); } // โหลดข้อมูลใหม่อีกครั้ง setState(() { print("wow"); _setPath(_currentFolder!); }); } } // จำลองสร้างไฟล์ใหม่ void _newFile() async { String filename = "${DateTime.now().millisecondsSinceEpoch}.txt"; String newFile = "${_currentFolder!.path}/${filename}"; final myfile = File(newFile); // กำหนด file object final isExits = await myfile.exists(); // เช็คว่ามีไฟล์หรือไม่ if(!isExits){ // ถ้ายังไม่มีไฟล์ try{ // สร้างไฟล์ text var file = await myfile.writeAsString( 'Hello World' ); print(file); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งจำลองการสร้างโฟลเดอร์ void _newFolder() async { String newFolder = "${_currentFolder!.path}/mydir"; final myDir = Directory(newFolder); // สร้าง directory object var isExits = await myDir.exists(); // เช็คว่ามีแล้วหรือไม่ if(!isExits){ // ถ้ายังไม่มีสร้างโฟลเดอร์ขึ้นมาใหม่ try{ var directory = await Directory(newFolder).create(recursive: true); print(directory); }catch(e){ print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่ง import ไฟล์ผ่าน file_picker void _importFile() async { try{ // FilePickerResult? result = await FilePicker.platform.pickFiles(); FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true); if (result != null) { // File _file = File(result.files.single.path!); List<File> _file = result.paths.map((path) => File(path!)).toList(); result.files.forEach((file) async { // วนลุป copy ไฟล์ไปยังโฟลเดอร์ที่ใช้งาน // กำหนด path ของไฟล์ใหม่ แล้วทำการ copy จาก cache ไปไว้ใน app String newPath = "${_currentFolder!.path}/${file.name}"; var cachefile = File(file.path!); // กำหนด file object var newFile = await cachefile.copy(newPath); // ข้อมูลไฟล์ print(file.name); print(file.bytes); print(file.size); print(file.extension); print(file.path); // ตัวสุดท้ายทำงานเสร็จ if(file == result.files.last){ // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } }); } else { print("User canceled the picker"); } }catch(e){ print(e); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Explorer'), actions: <Widget>[ // IconButton( onPressed: _importFile, // icon: FaIcon(FontAwesomeIcons.fileImport), ), IconButton( onPressed: _newFolder, // สร้างโฟลเดอร์ใหม่ icon: FaIcon(FontAwesomeIcons.folderPlus), ), IconButton( onPressed: _newFile, // สร้างไฟล์ใหม่ icon: FaIcon(FontAwesomeIcons.fileAlt), ), if(_selectedItems.isNotEmpty) IconButton( onPressed: _deleteAll, icon: FaIcon(FontAwesomeIcons.trashAlt), ), ], ), body: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ ListTile( leading: FaIcon(FontAwesomeIcons.angleLeft), title: Text('${_currentPath.replaceAll('/data/user/0/com.example.demo_app', '/')}'), onTap: (){ _setPath(_currentFolder!.parent); } ), Expanded( child: _folders!=null // เมื่อไม่ใช่ค่า null ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: _folders==null ? 0 : _folders!.length, itemBuilder: (context, index) { var isFolder = _folders![index] is Directory ? true : false; // เช็คว่าเป็นโฟลเดอร์ var isFile = _folders![index] is File ? true : false; // เช็คว่าเป็นไฟล์ if(_folders![index] != null){ // เอาเฉพาะชื่อหลัง / ตัวสุดท้าย String fileName = _folders![index]!.path.split('/').last; return Dismissible( direction: DismissDirection.horizontal, key: UniqueKey(), // dismissThresholds: const { DismissDirection.endToStart:1.0, DismissDirection.startToEnd:1.0}, confirmDismiss: (direction) async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete this item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE") ), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); }, onDismissed: (direction) { // ปัดไปทางขวา - บนลงล่าง if(direction == DismissDirection.startToEnd){ } // ปัดไปซ้าย - ล่างขึ้นบน if(direction == DismissDirection.endToStart){ try{ setState(() { if(isFile){ // ถ้าเป็นไฟล์ ส่ง path ไฟล์ไปลบ _deleteFile(_folders![index]!.path); } if(isFolder){ // ถ้าเป็นโฟลเดอร์ส่ง path โฟลเดอร์ไปลบ _deleteFolder(_folders![index]!.path); } // ต้องลบข้อมูลก่อน แล้วค่อยลบรายการในลิส _folders!.removeAt(index); }); }catch(e){ print(e); } } ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('$index dismissed'))); }, background: Container( color: Colors.green, ), secondaryBackground: Container( color: Colors.red, child: Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.symmetric(horizontal: 10.0), child: FaIcon(FontAwesomeIcons.trashAlt), ) ) ), child: ListTile( selected: _selectedItems.contains(index) ? true : false, leading: isFolder ? FaIcon(FontAwesomeIcons.solidFolder) : FaIcon(FontAwesomeIcons.file), trailing: Visibility( visible: _selectedItems.contains(index) ? true : false, child: FaIcon(FontAwesomeIcons.checkCircle), ), title: Text('${fileName}'), onLongPress: (){ if(! _selectedItems.contains(index)){ setState(() { _selectedItems.add(index); }); } }, onTap: (isFolder==true) ? (){ // กรณีเป้นโฟลเดอร์ if(_selectedItems.contains(index)){ setState(() { _selectedItems.removeWhere((val) => val == index); }); }else{ if(_selectedItems.isNotEmpty){ setState(() { _selectedItems.add(index); }); }else{ _setPath(_folders![index]!); // ถ้ากด ให้ทำคำสั่งเปิดโฟลเดอร์ } } } : (){ if(_selectedItems.contains(index)){ setState(() { _selectedItems.removeWhere((val) => val == index); }); }else{ if(_selectedItems.isNotEmpty){ setState(() { _selectedItems.add(index); }); }else{ Navigator.of(context).push(_viewFile(context, _folders![index]!)); } } }, // กรณีเป็นไฟล์ ) ); }else{ return Container(); } }, separatorBuilder: (BuildContext context, int index) => const Divider(height: 1,), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ), ], ), ); } // อ่านข้อมูลจากไฟล์ Future<String>? _readFile(file) async { var _text = ''; final _file = File(file.path); // แบบ ใช้ stream Stream<String> lines = _file.openRead() .transform(utf8.decoder) // Decode bytes to UTF-8. .transform(LineSplitter()); // Convert stream to individual lines. try { // แบบ ใช้ stream await for (var line in lines) { _text += '${line}n'; } // แบบ ไม่ใช้ stream // _text = await _file.readAsString(); print('File is now closed.'); } catch (e) { print(e); } setState(() { _textData.value = TextEditingValue(text: _text); }); return _text; } // บันทึกข้อมูลข้อความลงไฟล์ Future<File?> _saveFile(file, str) async { File? _file = File(file.path); try{ // แบบ ใช้ stream var sink = _file.openWrite(); sink.write(str); sink.close(); // แบบ ไม่ใช้ stream // await _file.writeAsString(str); }catch(e){ print(e); } return _file; } // สร้างฟังก์ชั่น ที่คืนค่าเป็น route ของ object ฟังก์ชั่นนี้ มี context และ product เป็น parameter Route<Object?> _viewFile(BuildContext context, FileSystemEntity file) { return DialogRoute<void>( context: context, builder: (context) { return Dismissible( // คืนค่าเป็น dismissible widget direction: DismissDirection.vertical, // เมื่อปัดลงในแนวตั้ง key: const Key('key'), // ต้องกำหนด key ใช้ค่าตามนี้ได้เลย onDismissed: (_) => Navigator.of(context).pop(), // ปัดลงเพื่อปิด child: Scaffold( appBar: AppBar( leading: IconButton( onPressed: (){ Navigator.of(context).pop(); }, icon: FaIcon(FontAwesomeIcons.times, color: Colors.black,), ), elevation: 0.0, actions: <Widget>[ // IconButton( onPressed: () async { FocusScope.of(context).unfocus(); // ยกเลิดโฟกัส ให้แป้นพิมพ์ซ่อนไป await _saveFile(file, _textData.text); // เขียนข้อมูลที่กรอกใหม่ลงไฟล์ // จำลองแสดงแจ้งเมื่อบันทึกสำเร็จ ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Save data successful'))); }, // สร้างโฟลเดอร์ใหม่ icon: FaIcon(FontAwesomeIcons.solidSave, color: Colors.black,), ), ], ), body: FutureBuilder<String?>( future: _readFile(file), // ข้อมูล future builder: (context, snapshot) { // สร้าง widget เมื่อได้ค่า snapshot ข้อมูลสุดท้าย if (snapshot.hasData) { // ถ้าได้ค่าข้อมูลสุดท้าย return Form( // ใช้งานฟอร์ม key: _formKey, // กำหนด key child: Container( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(0.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ TextFormField( controller: _textData, // ใช้ข้อความจาก controller maxLines: 50, keyboardType: TextInputType.multiline, decoration: InputDecoration( border: InputBorder.none, hintText: "Enter a message", fillColor: Colors.grey[30], filled: true, ), ), ], ), ), ) ), ); } else if (snapshot.hasError) { // ถ้ามี error return Text('${snapshot.error}'); } // ค่าเริ่มต้น, แสดงตัว Loading. return const Center(child: CircularProgressIndicator()); }, ), ), ); }, ); } }
ข้อมุลที่ถูกย้ายมาอยู่ในโฟลเดอร์ app_flutter ก็จะคงอยู่จนกว่าจะทำการลบ app ออกไปหรือทำการลบ
ข้อมูลนั้นด้วยคำสั่งที่ app กำหนด
แนวทางการใช้งาน file picker ในบทความนี้ สามารถนำไปปรับประยุกต์ใช้งานได้ตามต้องการ ไม่ว่าจะเป็น
การเลือกไฟล์จากมือถือผ่าน app เพื่ออัพโหลดขึ้นไปเก็บบน server เป็นต้น
เนื้อหาเกี่ยวกับการจัดการไฟล์ใน flutter เบื้องต้นก็จะขอจบเพียงเท่านี้ รูปแบบและหน้าตาการใช้งานของ
บทความในตัวอย่างตอนหน้าก็จะใช้รูปแบบใหม่
ก่อนจบ ขอเสริม package ที่ชื่อ open_file ที่เราสามารถนำมาใช้งาน กรณีต้องการเปิดไฟล์ด้วย app ที่มี
ในเครื่อง หรือ app ที่รองรับ ด้วยคำสั่งง่ายๆ ดูรายละเอียดเพิ่มเติมที่หน้าเพจ ของ package