การใช้งาน Path Provider และการเชียนอ่าน File ใน Flutter

เขียนเมื่อ 2 ปีก่อน โดย Ninenik Narkdee
flutter path provider file folder

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ flutter path_provider file folder

ดูแล้ว 7,523 ครั้ง




เราได้รู้จักการเก็บข้อมูลแบบถาวรไว้ใน app มาแล้ว
ไม่ว่าจะเป็นการใช้งาน ฐานข้อมูล หรือการใช้งาน  shared 
preferences ในรุปแบบข้อมูล key-value เนื้อหานี้จะมาดูเกี่ยว
กับการใช้งานไฟล์ เป็นอีกรูปแบบของการเก็บข้อมูล 
 
  *เนื้อหานี้ใช้เนื้อหาต่อเนื่องจากบทความ http://niik.in/960
 
 

การใช้งาน Path Provider

    เป็น package ที่เราจะจัดการตำแหน่งของข้อมูลไฟล์ใน app หรือก็คือ path ต่างๆ
ของ app ก่อนใช้งานต้องทำการติดตั้งดังนี้
 

    ติดตั้ง path_provider กำหนดในไฟล์ pubspec.yaml

 
 
dependencies:
  path_provider: ^2.1.4
 
    จากนั้น import เข้ามาใช้งานในหน้าที่ต้องการด้วยคำสั่ง
 
import 'package:path_provider/path_provider.dart';
 
    การทำงานหลักๆ ของ package นี้ก็คือคำสั่งที่จะดึงข้อมูล path ใน app ที่เราต้องการใช้งาน 
รองรับทั้งระบบ ios และ android ซึ่งเราจะดูเฉพาะส่วนของ android 
    การใช้งาน path provider จะรองรับทั้งข้อมูลภายในและภายนอกหรือ Internal /External Storage
ข้อมูลหลักๆ ที่เกี่ยวข้องก็จะเป็น Directory และ File หรือก็คือโฟเดอร์ และ ไฟล์
 
 

    คำสั่งการทำงานของ path_provider

    ดูคำสั่งการทำงานและค่าของข้อมูลที่ได้เมื่อใช้งาน
 
    // ส่วนของ Internal Storage

final tempDirectory = await getTemporaryDirectory();
// Directory: '/data/user/0/com.example.demo_app/cache'

final appSupportDirectory = await getApplicationSupportDirectory();
// Directory: '/data/user/0/com.example.demo_app/files'

final appDocumentsDirectory = await getApplicationDocumentsDirectory();
// Directory: '/data/user/0/com.example.demo_app/app_flutter'

    // ส่วนของ External Storage

final externalDocumentsDirectory = await getExternalStorageDirectory();
// Directory: '/storage/emulated/0/Android/data/com.example.demo_app/files'

final externalCacheDirectories = await getExternalCacheDirectories();
// [Directory: '/storage/emulated/0/Android/data/com.example.demo_app/cache', 
// Directory: '/storage/1818-160B/Android/data/com.example.demo_app/cache']

final externalStorageDirectories = await getExternalStorageDirectories(type: StorageDirectory.music);
// [Directory: '/storage/emulated/0/Android/data/com.example.demo_app/files/Music', 
// Directory: '/storage/1818-160B/Android/data/com.example.demo_app/files/Music']
 
ข้อมูล storage/emulated/0/ จริงๆ แล้วคือที่เก็บข้อมูลภายใน (internal storage) ที่สามารถเข้าถึงได้โดยผู้ใช้
(user-accessible internal storage) ส่วน External storage แบบที่เป็นจริงๆ จะเป็นการ์ด SD (SD card)
ที่สามารถถอดออกจากอุปกรณ์ได้  
    ข้อมูล External Storage จะรองรับก็ต่อเมื่อมีการใช้งาน เช่น สมมติเราไม่มีการใช้งาน SD Card หรือ
ที่จัดเก็บข้อมูลภายนอก ก็จะไม่สามารถเรียกดูข้อมูลส่วนนี้ได้ โฟลเดอร์หลักที่เราสามารถเข้าถึงได้ก็คือ
โฟลเดอร์ที่เป็นชื่อ package ของ app ทั้งแบบ internal และ external ในตัวอย่างก็คือโฟลเดอร์ชื่อ com.example.demo_app โดยทั่วไปเราจะไม่ได้สิทธิ์เข้าถึงโฟลเดอร์ที่อยู่เหนือกว่านี้ได้ ข้อมูลชั่วคราว
ที่เกิดขึ้นระหว่างใช้งาน app จะถูกเก็บไว้ในโฟลเดอร์ cache หรือเราเรียกว่า Temporary directory สามารถ
ถูกลบได้ตลอดเวลา ดังนั้นในส่วนนี้จะไม่ใช้เก็บข้อมูลที่สำคัญๆ ไว้   
    ในทุกๆ flutter app ส่วนใหญ่จะมี Documents directory อยู่ในโฟลเดอร์ชื่อ app_flutter เป็นที่สำหรับ
เก็บไฟล์ที่ตัว app เท่านั้นสามารถเรียกใช้งานได้ ใน android จะเรียกว่า AppData directory  ข้อมูลจะลบ
ก็ต่อเมื่อเราทำการลบ app ออกจากเครื่อง
 
    ตัวอย่างโค้ดไฟล์ explorer.dart ต่อไปนี้ เราจะจำลองแสดงรายการข้อมูลไฟล์ในโฟลเดอร์ package ของ app
ซึ่งปกติแล้ว โครงสร้างข้อมูลพวกนี้ เราจะใช้ app ใช้งานเท่านั้น ไม่ได้นำมาแสดงให้ผู้ใช้ทั่วไปเห็นหรือใช้งาน
แต่ในที่นี้ต้องการนำมาแสดงเป็นแนวทางให้เห็นโครงสร้างไฟล์และโฟลเดอร์ของ app เท่านั้น
 

    ไฟล์ explorer.dart

 
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:path_provider/path_provider.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; // เก็บ โฟลเดอร์ที่กำลังใช้งาน
  
    @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);
      });
  
    }       
  
    // เปิดโฟลเดอร์ และแสดงรายการในโฟลเดอร์
    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);
      }
    }
  
    // จำลองสร้างไฟล์ใหม่
    void _newFile() async {
      String newFile = "${_currentFolder!.path}/myfile.txt";
      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);
        }
      }else{ // ถ้ามีไฟล์อยู่แล้ว จำลองการลบข้อมูล
        try{
          await myfile.delete();
        }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);
        }
      }else{ // ถ้ามีแล้วจำลองการทำคำสั่งลบ
        try{
          await myDir.delete(recursive: true);
        }catch(e){
          print(e);
        }
        
      }
      // โหลดข้อมูลใหม่อีกครั้ง    
      setState(() {
        _setPath(_currentFolder!);
      });
    }

    @override
    Widget build(BuildContext context) {
  
        return Scaffold(
            appBar: AppBar(
                title: Text('Explorer'),
                actions: <Widget>[ // 
                  IconButton(
                    onPressed: _newFolder, // สร้างโฟลเดอร์ใหม่
                    icon: FaIcon(FontAwesomeIcons.folderPlus), 
                  ),
                  IconButton(
                    onPressed: _newFile, // สร้างไฟล์ใหม่
                    icon: FaIcon(FontAwesomeIcons.fileAlt), 
                  ),
                ],
            ),
            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 ListTile(
                                    leading: isFolder
                                    ? FaIcon(FontAwesomeIcons.solidFolder)
                                    : FaIcon(FontAwesomeIcons.file),
                                    title: Text('${fileName}'),
                                    onTap: (isFolder==true)
                                      ? (){ // กรณีเป้นโฟลเดอร์
                                        _setPath(_folders![index]!); // ถ้ากด ให้ทำคำสั่งเปิดโฟลเดอร์
                                      }
                                      : (){}, // กรณีเป็นไฟล์ 
                                  );
                                }else{
                                  return Container();
                                }
                              },
                              separatorBuilder: (BuildContext context, int index) => const Divider(height: 1,),                    
                            )
                          : const Center(child: Text('No items')), // กรณีไม่มีรายการ
                        ),
                    ],
            ),
        );
    }
}
 
    ตัวอย่างผลลัพธ์และการทำงาน
 


 
 
    ในตัวอย่างการทำงาน เราเริ่มต้นที่ package โฟลเดอร์ ในที่นี้คือ com.example.demo_app โดยมี action
icon มุมบนขวาสองรายการคือ สร้างโฟลเดอร์ใหม่ กับ สร้างไฟล์ใหม่ ซึ่งถ้ากดซ้ำก็จะเป็นการลบตัวที่เคยสร้าง
เป็นการจำลองการทำงานสำหรับการลบข้อมูล
    ส่วนบนของลิสรายการข้อมูลไฟล์และโฟลเดอร์จะแสดง path ปัจจุบัน จะตัด path ที่อยู่ระดับบนกว่าออก
แสดงเฉพาะ path ต่อจาก package โฟลเดอร์  ถ้ากดที่รายการนี้จะเป็นการย้อนกลับไปโฟลเดอร์ระดับบน
หรือก็คือถอยกลับไปข้างบนหนึ่งระดับ
    สำหรับลิสรายการโฟลเดอร์หรือไฟล์ ถ้าเป็นโฟลเดอร์เมื่อกด จะเป็นการเปิดโฟลเดอร์นั้น และตั้งเป็นโฟลเดอร์
ปัจจุบันที่กำลังใช้งาน ถ้าเป็นไฟล์ในที่นี้ยังไม่กำหนดการทำงาน
    ตัวอย่างการทำงานทั้งหมดนี้ เราต้องการให้เห็นแนวทาง และวิธีการจัดการเกี่ยวกับไฟล์และโฟลเดอร์ เช่น
สร้างโฟลเดอร์/ไฟล์ใหม่ ใช้งาน และ ลบโฟลเดอร์/ไฟล์ รวมถึงการนำเอา path_provider มาใช้ในการหาตำแหน่ง
ของโฟลเดอร์ต่างๆ ในระบบ android 
 
    เนื้อหานี้จะมีรายละเอียดเพิ่มเติมในตอนหน้า เกี่ยวกับการจัดการไฟล์ และอื่นๆ  อย่าลืมว่าระบบการจัดการไฟล์นี้
เป็นส่วนของการทำงานของ app ที่เราอาจจะใช้งาน ไม่ใช่ส่วนที่ผู้ใช้ทั่วไปจะสามารถเข้าถึงแบบเต็มรูปแบบ แต่ที่ยก
มาก็เพื่อให้เห็นโครงสร้างและการทำงาน ตัวอย่างการนำไปใช้ ก็เช่น เรามีไฟล์ json บน server ที่อาจจะอัพเดท
ทุกๆ วันหรือวันละครั้ง และเราต้องการให้ผู้ใช้สามารถดูข้อมูลแบบออฟไลน์ได้ ถ้าไม่ได้ต่อเน็ต เราก็ทำการดาวน์โหลด
ไฟล์ json นั้นลงมาบันทึกไว้ที่เครื่อง และกำหนดให้เรียกใช้งานเมื่อออฟไลน์ เป็นต้น
 
    สำหรับเนื้อหาในบทความนี้ เราใช้ส่วนของ app เริ่มต้นจากบทความด้านล่าง ทบทวนได้ที่
    การใช้งาน Drawer กำหนด SideMenu ใน Flutter เบื้องต้น http://niik.in/960
 

 
 
     เนื้อหาตอนหน้าจะเป็นอะไร รอติดตามการประยุกต์ต่อเพิ่มเติมในตอนหน้า
*กรณีลบไฟล์ได้ ดูที่บ่ทความ http://niik.in/1068


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 04-08-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

http://niik.in/download/flutter/demo_031_04082024_source.rar


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ