เกี่ยวกับ Codelab นี้
1 บทนำ
FFI (Foreign Function Interface) ของ Dart ช่วยให้แอป Flutter ใช้ประโยชน์จากไลบรารีเนทีฟที่มีอยู่ซึ่งแสดง C API ได้ Dart รองรับ FFI ใน Android, iOS, Windows, macOS และ Linux สําหรับเว็บ Dart รองรับการทำงานร่วมกันของ JavaScript แต่หัวข้อนี้ไม่อยู่ใน Codelab นี้
สิ่งที่คุณจะสร้าง
ในโค้ดแล็บนี้ คุณจะได้สร้างปลั๊กอินสำหรับอุปกรณ์เคลื่อนที่และเดสก์ท็อปที่ใช้ไลบรารี C คุณจะใช้ API นี้เพื่อเขียนแอปตัวอย่างที่ใช้ปลั๊กอิน ปลั๊กอินและแอปจะทําสิ่งต่อไปนี้
- นําเข้าซอร์สโค้ดของไลบรารี C ไปยังปลั๊กอิน Flutter ใหม่
- ปรับแต่งปลั๊กอินเพื่อให้สร้างได้ใน Windows, macOS, Linux, Android และ iOS
- สร้างแอปพลิเคชันที่ใช้ปลั๊กอินสําหรับ REPL (read reveal print loop) ของ JavaScript
สิ่งที่คุณจะได้เรียนรู้
ในโค้ดแล็บนี้ คุณจะได้เรียนรู้ความรู้เชิงปฏิบัติที่จำเป็นต่อการสร้างปลั๊กอิน Flutter ที่ใช้ FFI ทั้งบนแพลตฟอร์มเดสก์ท็อปและอุปกรณ์เคลื่อนที่ ซึ่งรวมถึงสิ่งต่อไปนี้
- การสร้างเทมเพลตปลั๊กอิน Flutter ที่ใช้ Dart FFI
- การใช้แพ็กเกจ
ffigen
เพื่อสร้างโค้ดการเชื่อมโยงสำหรับไลบรารี C - การใช้ CMake เพื่อสร้างปลั๊กอิน FFI ของ Flutter สำหรับ Android, Windows และ Linux
- การใช้ CocoaPods เพื่อสร้างปลั๊กอิน Flutter FFI สำหรับ iOS และ macOS
สิ่งที่คุณต้องมี
- Android Studio 4.1 ขึ้นไปสำหรับการพัฒนาแอป Android
- Xcode 13 ขึ้นไปสำหรับการพัฒนา iOS และ macOS
- Visual Studio 2022 หรือ Visual Studio Build Tools 2022 ที่มีเวิร์กโหลด "การพัฒนาเดสก์ท็อปด้วย C++" สําหรับการพัฒนาเดสก์ท็อป Windows
- Flutter SDK
- เครื่องมือสร้างที่จำเป็นสำหรับแพลตฟอร์มที่คุณจะใช้พัฒนา (เช่น CMake, CocoaPods และอื่นๆ)
- LLVM สําหรับแพลตฟอร์มที่คุณจะพัฒนา
ffigen
ใช้ชุดเครื่องมือคอมไพเลอร์ LLVM เพื่อแยกวิเคราะห์ไฟล์ส่วนหัว C เพื่อสร้างการเชื่อมโยง FFI ที่แสดงใน Dart - ตัวแก้ไขโค้ด เช่น Visual Studio Code
2 เริ่มต้นใช้งาน
เครื่องมือ ffigen
เป็นเครื่องมือที่เพิ่งเพิ่มเข้ามาใน Flutter คุณสามารถยืนยันว่าการติดตั้ง Flutter ของคุณใช้รุ่นเสถียรปัจจุบันอยู่โดยเรียกใช้คำสั่งต่อไปนี้
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.32.4, on macOS 15.5 24F74 darwin-arm64, locale en-AU) [✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 16.4) [✓] Chrome - develop for the web [✓] Android Studio (version 2024.2) [✓] IntelliJ IDEA Community Edition (version 2024.3.1.1) [✓] VS Code (version 1.101.0) [✓] Connected device (3 available) [✓] Network resources • No issues found!
ตรวจสอบว่าเอาต์พุต flutter doctor
ระบุว่าคุณใช้เวอร์ชันเสถียร และไม่มี Flutter เวอร์ชันเสถียรที่ใหม่กว่าพร้อมใช้งาน หากคุณไม่ได้ใช้เวอร์ชันเสถียรหรือมีรุ่นที่ใหม่กว่า ให้เรียกใช้ 2 คำสั่งต่อไปนี้เพื่ออัปเดตเครื่องมือ Flutter
flutter channel stable flutter upgrade
คุณเรียกใช้โค้ดในโค้ดแล็บนี้ได้โดยใช้อุปกรณ์ต่อไปนี้
- คอมพิวเตอร์สำหรับการพัฒนา (สำหรับบิลด์เดสก์ท็อปของปลั๊กอินและแอปตัวอย่าง)
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาแอป
- เครื่องจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องมีการตั้งค่าใน Android Studio)
3 สร้างเทมเพลตปลั๊กอิน
เริ่มต้นใช้งานการพัฒนาปลั๊กอิน Flutter
Flutter มาพร้อมกับเทมเพลตสำหรับปลั๊กอินที่จะช่วยให้คุณเริ่มต้นใช้งานได้ เมื่อสร้างเทมเพลตปลั๊กอิน คุณจะระบุภาษาที่ต้องการใช้ได้
เรียกใช้คําสั่งต่อไปนี้ในไดเรกทอรีทํางานเพื่อสร้างโปรเจ็กต์โดยใช้เทมเพลตปลั๊กอิน
flutter create --template=plugin_ffi --platforms=android,ios,linux,macos,windows ffigen_app
พารามิเตอร์ --platforms
จะระบุแพลตฟอร์มที่พลิแกนจะรองรับ
คุณสามารถตรวจสอบเลย์เอาต์ของโปรเจ็กต์ที่สร้างขึ้นได้โดยใช้คำสั่ง tree
หรือโปรแกรมสำรวจไฟล์ของระบบปฏิบัติการ
$ tree -L 2 ffigen_app ffigen_app ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android │ ├── build.gradle │ ├── ffigen_app_android.iml │ ├── local.properties │ ├── settings.gradle │ └── src ├── example │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ ├── ffigen_app_example.iml │ ├── ios │ ├── lib │ ├── linux │ ├── macos │ ├── pubspec.lock │ ├── pubspec.yaml │ └── windows ├── ffigen.yaml ├── ffigen_app.iml ├── ios │ ├── Classes │ └── ffigen_app.podspec ├── lib │ ├── ffigen_app.dart │ └── ffigen_app_bindings_generated.dart ├── linux │ └── CMakeLists.txt ├── macos │ ├── Classes │ └── ffigen_app.podspec ├── pubspec.lock ├── pubspec.yaml ├── src │ ├── CMakeLists.txt │ ├── ffigen_app.c │ └── ffigen_app.h └── windows └── CMakeLists.txt 17 directories, 26 files
คุณควรใช้เวลาสักครู่เพื่อดูโครงสร้างไดเรกทอรีเพื่อให้ทราบสิ่งที่สร้างขึ้นและตำแหน่งของสิ่งนั้น เทมเพลต plugin_ffi
จะวางโค้ด Dart สําหรับปลั๊กอินไว้ในส่วน lib
ซึ่งเป็นไดเรกทอรีเฉพาะแพลตฟอร์มที่มีชื่อว่า android
, ios
, linux
, macos
และ windows
และที่สำคัญที่สุดคือไดเรกทอรี example
สําหรับนักพัฒนาซอฟต์แวร์ที่คุ้นเคยกับการพัฒนา Flutter แบบปกติ โครงสร้างนี้อาจดูแปลกๆ เนื่องจากไม่มีไฟล์ปฏิบัติการที่กําหนดไว้ที่ระดับบนสุด ปลั๊กอินมีไว้เพื่อรวมไว้ในโปรเจ็กต์ Flutter อื่นๆ แต่คุณจะเขียนโค้ดในไดเรกทอรี example
เพื่อยืนยันว่าโค้ดปลั๊กอินใช้งานได้
ได้เวลาเริ่มต้นใช้งานแล้ว
4 สร้างและเรียกใช้ตัวอย่าง
โปรดตรวจสอบว่าได้ติดตั้งระบบบิลด์และข้อกําหนดเบื้องต้นอย่างถูกต้องและใช้งานได้กับแพลตฟอร์มที่รองรับแต่ละแพลตฟอร์มแล้ว โดยให้สร้างและเรียกใช้แอปตัวอย่างที่สร้างขึ้นสําหรับแต่ละเป้าหมาย
Windows
ตรวจสอบว่าคุณใช้ Windows เวอร์ชันที่รองรับ เป็นที่ทราบกันดีว่าโค้ดแล็บนี้ใช้งานได้ใน Windows 10 และ Windows 11
คุณสามารถสร้างแอปพลิเคชันจากภายในเครื่องมือแก้ไขโค้ดหรือในบรรทัดคำสั่งก็ได้
PS C:\Users\brett\Documents> cd .\ffigen_app\example\ PS C:\Users\brett\Documents\ffigen_app\example> flutter run -d windows Launching lib\main.dart on Windows in debug mode...Building Windows application... Syncing files to device Windows... 160ms Flutter run key commands. r Hot reload. R Hot restart. h List all available interactive commands. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). Running with sound null safety An Observatory debugger and profiler on Windows is available at: http://127.0.0.1:53317/OiKWpyHXxHI=/ The Flutter DevTools debugger and profiler on Windows is available at: http://127.0.0.1:9100?uri=http://127.0.0.1:53317/OiKWpyHXxHI=/
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ดังต่อไปนี้
Linux
ตรวจสอบว่าคุณใช้ Linux เวอร์ชันที่รองรับ Codelab นี้ใช้ Ubuntu 22.04.1
เมื่อติดตั้งข้อกําหนดเบื้องต้นทั้งหมดที่ระบุไว้ในขั้นตอนที่ 2 แล้ว ให้เรียกใช้คําสั่งต่อไปนี้ในเทอร์มินัล
$ cd ffigen_app/example $ flutter run -d linux Launching lib/main.dart on Linux in debug mode... Building Linux application... Syncing files to device Linux... 504ms Flutter run key commands. r Hot reload. 🔥🔥🔥 R Hot restart. h List all available interactive commands. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). 💪 Running with sound null safety 💪 An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:36653/Wgek1JGag48=/ The Flutter DevTools debugger and profiler on Linux is available at: http://127.0.0.1:9103?uri=http://127.0.0.1:36653/Wgek1JGag48=/
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ดังต่อไปนี้
Android
สำหรับ Android คุณใช้ Windows, macOS หรือ Linux ในการคอมไพล์ได้
คุณต้องทําการเปลี่ยนแปลงใน example/android/app/build.gradle.kts
เพื่อใช้ NDK เวอร์ชันที่เหมาะสม
example/android/app/build.gradle.kts)
android {
// Modify the next line from `flutter.ndkVersion` to the following:
ndkVersion = "27.0.12077973"
// ...
}
ตรวจสอบว่าคุณมีอุปกรณ์ Android ที่เชื่อมต่อกับคอมพิวเตอร์สำหรับการพัฒนาหรือกำลังเรียกใช้อินสแตนซ์ Android Emulator (AVD) ตรวจสอบว่า Flutter เชื่อมต่อกับอุปกรณ์ Android หรือโปรแกรมจำลองได้โดยการเรียกใช้คำสั่งต่อไปนี้
$ flutter devices 3 connected devices: sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 12 (API 32) (emulator) macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
เมื่อคุณมีอุปกรณ์ Android ที่ทำงานอยู่ ไม่ว่าจะเป็นอุปกรณ์จริงหรือโปรแกรมจำลอง ให้เรียกใช้คำสั่งต่อไปนี้
cd ffigen_app/example flutter run
Flutter จะถามคุณว่าต้องการเรียกใช้บนอุปกรณ์ใด เลือกอุปกรณ์ที่เหมาะสมจากรายการ
macOS และ iOS
สำหรับการพัฒนา Flutter ใน macOS และ iOS คุณต้องใช้คอมพิวเตอร์ macOS
เริ่มต้นด้วยการเปิดแอปตัวอย่างใน macOS ตรวจสอบอีกครั้งว่า Flutter เห็นอุปกรณ์ใดบ้าง
$ flutter devices 2 connected devices: macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
เรียกใช้แอปตัวอย่างโดยใช้โปรเจ็กต์ปลั๊กอินที่สร้างขึ้น
cd ffigen_app/example flutter run -d macos
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ดังต่อไปนี้
สำหรับ iOS คุณสามารถใช้โปรแกรมจำลองหรืออุปกรณ์ฮาร์ดแวร์จริง หากใช้เครื่องจำลอง ให้เปิดเครื่องจำลองก่อน ตอนนี้คำสั่ง flutter devices
จะแสดงเครื่องจำลองเป็นหนึ่งในอุปกรณ์ที่ใช้ได้
$ flutter devices 3 connected devices: iPhone SE (3rd generation) (mobile) • 1BCBE334-7EC4-433A-90FD-1BC14F3BA41F • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-1 (simulator) macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
เมื่อคุณมีอุปกรณ์ iOS ที่ทำงานอยู่ ไม่ว่าจะเป็นอุปกรณ์จริงหรือเครื่องจำลอง ให้เรียกใช้คำสั่งต่อไปนี้
cd ffigen_app/example flutter run
Flutter จะถามคุณว่าต้องการเรียกใช้บนอุปกรณ์ใด เลือกอุปกรณ์ที่เหมาะสมจากรายการ
เครื่องจำลอง iOS จะมีความสําคัญเหนือกว่าเป้าหมาย macOS คุณจึงข้ามการระบุอุปกรณ์ด้วยพารามิเตอร์ -d
ได้
ยินดีด้วย คุณสร้างและเรียกใช้แอปพลิเคชันในระบบปฏิบัติการ 5 ระบบที่แตกต่างกันเรียบร้อยแล้ว ขั้นตอนถัดไปคือการสร้างปลั๊กอินแบบเนทีฟและการติดต่อกับปลั๊กอินจาก Dart โดยใช้ FFI
5 ใช้ Duktape ใน Windows, Linux และ Android
ไลบรารี C ที่คุณจะใช้ในโค้ดแล็บนี้คือ Duktape Duktape เป็นเครื่องมือ JavaScript ที่ฝังได้ โดยเน้นที่ความสามารถในการเคลื่อนย้ายได้ง่ายและขนาดที่กะทัดรัด ในขั้นตอนนี้ คุณจะต้องกำหนดค่าปลั๊กอินให้คอมไพล์ไลบรารี Duktape, ลิงก์กับปลั๊กอิน แล้วเข้าถึงโดยใช้ FFI ของ Dart
ขั้นตอนนี้จะกำหนดค่าการผสานรวมให้ทำงานใน Windows, Linux และ Android การผสานรวม iOS และ macOS ต้องมีการกําหนดค่าเพิ่มเติม (นอกเหนือจากที่ระบุไว้ในขั้นตอนนี้) เพื่อรวมไลบรารีที่คอมไพล์แล้วไว้ในไฟล์ปฏิบัติการ Flutter ขั้นสุดท้าย การกำหนดค่าเพิ่มเติมที่จำเป็นจะอยู่ในขั้นตอนถัดไป
ดึงข้อมูล Duktape
ก่อนอื่น ให้ขอสำเนาซอร์สโค้ด duktape
โดยดาวน์โหลดจากเว็บไซต์ duktape.org
สำหรับ Windows คุณสามารถใช้ PowerShell กับ Invoke-WebRequest
ดังนี้
PS> Invoke-WebRequest -Uri https://duktape.org/duktape-2.7.0.tar.xz -OutFile duktape-2.7.0.tar.xz
สําหรับ Linux wget
เป็นตัวเลือกที่ดี
$ wget https://duktape.org/duktape-2.7.0.tar.xz --2022-12-22 16:21:39-- https://duktape.org/duktape-2.7.0.tar.xz Resolving duktape.org (duktape.org)... 104.198.14.52 Connecting to duktape.org (duktape.org)|104.198.14.52|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1026524 (1002K) [application/x-xz] Saving to: 'duktape-2.7.0.tar.xz' duktape-2.7.0.tar.x 100%[===================>] 1002K 1.01MB/s in 1.0s 2022-12-22 16:21:41 (1.01 MB/s) - 'duktape-2.7.0.tar.xz' saved [1026524/1026524]
ไฟล์เป็นไฟล์ tar.xz
ใน Windows ตัวเลือกหนึ่งคือดาวน์โหลดเครื่องมือ 7Zip และใช้ดังนี้
PS> 7z x .\duktape-2.7.0.tar.xz 7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15 Scanning the drive for archives: 1 file, 1026524 bytes (1003 KiB) Extracting archive: .\duktape-2.7.0.tar.xz -- Path = .\duktape-2.7.0.tar.xz Type = xz Physical Size = 1026524 Method = LZMA2:26 CRC64 Streams = 1 Blocks = 1 Everything is Ok Size: 19087360 Compressed: 1026524
คุณต้องเรียกใช้ 7z 2 ครั้ง โดยครั้งแรกเพื่อแตกไฟล์ที่บีบอัดด้วย xz และครั้งที่ 2 เพื่อขยายไฟล์เก็บถาวร tar
PS> 7z x .\duktape-2.7.0.tar 7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15 Scanning the drive for archives: 1 file, 19087360 bytes (19 MiB) Extracting archive: .\duktape-2.7.0.tar -- Path = .\duktape-2.7.0.tar Type = tar Physical Size = 19087360 Headers Size = 543232 Code Page = UTF-8 Characteristics = GNU ASCII Everything is Ok Folders: 46 Files: 1004 Size: 18281564 Compressed: 19087360
ในสภาพแวดล้อม Linux สมัยใหม่ tar
จะดึงข้อมูลเนื้อหาในขั้นตอนเดียว ดังนี้
$ tar xvf duktape-2.7.0.tar.xz x duktape-2.7.0/ x duktape-2.7.0/README.rst x duktape-2.7.0/Makefile.sharedlibrary x duktape-2.7.0/Makefile.coffee x duktape-2.7.0/extras/ x duktape-2.7.0/extras/README.rst x duktape-2.7.0/extras/module-node/ x duktape-2.7.0/extras/module-node/README.rst x duktape-2.7.0/extras/module-node/duk_module_node.h x duktape-2.7.0/extras/module-node/Makefile [... and many more files]
ติดตั้ง LLVM
หากต้องการใช้ ffigen
คุณต้องติดตั้ง LLVM ซึ่ง ffigen
ใช้เพื่อแยกวิเคราะห์ส่วนหัว C ใน Windows ให้เรียกใช้คำสั่งต่อไปนี้
PS> winget install -e --id LLVM.LLVM Found LLVM [LLVM.LLVM] Version 15.0.5 This application is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Downloading https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.5/LLVM-15.0.5-win64.exe ██████████████████████████████ 277 MB / 277 MB Successfully verified installer hash Starting package install... Successfully installed
กำหนดค่าเส้นทางของระบบเพื่อเพิ่ม C:\Program Files\LLVM\bin
ลงในเส้นทางการค้นหาไบนารีเพื่อติดตั้ง LLVM ในเครื่อง Windows ให้เสร็จสมบูรณ์ คุณทดสอบได้ว่าติดตั้งอย่างถูกต้องหรือไม่โดยทำดังนี้
PS> clang --version clang version 15.0.5 Target: x86_64-pc-windows-msvc Thread model: posix InstalledDir: C:\Program Files\LLVM\bin
สำหรับ Ubuntu คุณสามารถติดตั้งข้อกำหนดของ LLVM ได้โดยทำดังนี้ ส่วนระบบปฏิบัติการ Linux อื่นๆ จะมี Dependency ที่คล้ายกันสำหรับ LLVM และ Clang
$ sudo apt install libclang-dev [sudo] password for brett: Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libclang-15-dev The following NEW packages will be installed: libclang-15-dev libclang-dev 0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded. Need to get 26.1 MB of archives. After this operation, 260 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-15-dev amd64 1:15.0.2-1 [26.1 MB] Get:2 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-dev amd64 1:15.0-55.1ubuntu1 [2962 B] Fetched 26.1 MB in 7s (3748 kB/s) Selecting previously unselected package libclang-15-dev. (Reading database ... 85898 files and directories currently installed.) Preparing to unpack .../libclang-15-dev_1%3a15.0.2-1_amd64.deb ... Unpacking libclang-15-dev (1:15.0.2-1) ... Selecting previously unselected package libclang-dev. Preparing to unpack .../libclang-dev_1%3a15.0-55.1ubuntu1_amd64.deb ... Unpacking libclang-dev (1:15.0-55.1ubuntu1) ... Setting up libclang-15-dev (1:15.0.2-1) ... Setting up libclang-dev (1:15.0-55.1ubuntu1) ...
ดังที่กล่าวไว้ข้างต้น คุณสามารถทดสอบการติดตั้ง LLVM ใน Linux ได้โดยทำดังนี้
$ clang --version Ubuntu clang version 15.0.2-1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
กำหนดค่า ffigen
pubpsec.yaml
ระดับบนสุดที่เทมเพลตสร้างขึ้นอาจมีแพ็กเกจ ffigen
เวอร์ชันเก่า เรียกใช้คำสั่งต่อไปนี้เพื่ออัปเดต Dependency ของ Dart ในโปรเจ็กต์ปลั๊กอิน
flutter pub upgrade --major-versions
เมื่อแพ็กเกจ ffigen
เป็นเวอร์ชันล่าสุดแล้ว ให้กำหนดค่าไฟล์ที่ ffigen
จะใช้เพื่อสร้างไฟล์การเชื่อมโยง แก้ไขเนื้อหาของไฟล์ ffigen.yaml
ของโปรเจ็กต์ให้ตรงกับข้อมูลต่อไปนี้
ffigen.yaml
# Run with `dart run ffigen --config ffigen.yaml`.
name: DuktapeBindings
description: |
Bindings for `src/duktape.h`.
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
output: 'lib/duktape_bindings_generated.dart'
headers:
entry-points:
- 'src/duktape.h'
include-directives:
- 'src/duktape.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full
ignore-source-errors: true
การกําหนดค่านี้ประกอบด้วยไฟล์ส่วนหัว C เพื่อส่งไปยัง LLVM, ไฟล์เอาต์พุตที่จะสร้าง, คําอธิบายที่จะวางไว้ที่ด้านบนของไฟล์ และส่วนนําหน้าที่ใช้เพื่อเพิ่มคําเตือน Lint
มีรายการการกําหนดค่า 1 รายการที่ท้ายไฟล์ซึ่งควรได้รับการอธิบายเพิ่มเติม ตั้งแต่เวอร์ชัน 11.0.0 ของ ffigen
เป็นต้นไป เครื่องมือสร้างการเชื่อมโยงจะไม่สร้างการเชื่อมโยงโดยค่าเริ่มต้นหากมีคำเตือนหรือข้อผิดพลาดที่เกิดจาก clang
เมื่อแยกวิเคราะห์ไฟล์ส่วนหัว
ไฟล์ส่วนหัวของ Duktape ตามที่เขียนไว้จะทริกเกอร์ clang
ใน macOS ให้แสดงคำเตือนเนื่องจากไม่มีตัวระบุประเภทที่อนุญาตค่า Null ในพอยน์เตอร์ของ Duktape หากต้องการรองรับ macOS และ iOS อย่างเต็มรูปแบบ Duktape จะต้องเพิ่มตัวระบุประเภทเหล่านี้ลงในโค้ดเบสของ Duktape ในระหว่างนี้ เราจะตัดสินใจที่จะไม่สนใจคำเตือนเหล่านี้โดยตั้งค่า Flag ignore-source-errors
เป็น true
ในแอปพลิเคชันเวอร์ชันที่ใช้งานจริง คุณควรแก้ไขคำเตือนทั้งหมดจากคอมไพเลอร์ก่อนส่งแอปพลิเคชัน อย่างไรก็ตาม การทำเช่นนี้สําหรับ Duktape อยู่นอกเหนือขอบเขตของโค้ดแล็บนี้
ดูรายละเอียดเพิ่มเติมเกี่ยวกับคีย์และค่าอื่นๆ ได้ในเอกสารประกอบffigen
คุณต้องคัดลอกไฟล์ Duktape ที่เฉพาะเจาะจงจากชุดการแจกจ่าย Duktape ไปยังตำแหน่งที่กําหนดค่า ffigen
ให้ค้นหาไฟล์เหล่านั้น
cp duktape-2.7.0/src/duktape.c src/ cp duktape-2.7.0/src/duktape.h src/ cp duktape-2.7.0/src/duk_config.h src/
ในทางเทคนิคแล้ว คุณจะต้องคัดลอก duktape.h
สำหรับ ffigen
เท่านั้น แต่คุณกำลังจะกำหนดค่า CMake เพื่อสร้างไลบรารีที่ต้องใช้ทั้ง 3 รายการ เรียกใช้ ffigen
เพื่อสร้างการเชื่อมโยงใหม่
$ dart run ffigen --config ffigen.yaml Building package executable... (1.5s) Built ffigen:ffigen. [INFO] : Running in Directory: '/Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05' [INFO] : Input Headers: [file:///Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05/src/duktape.h] [WARNING]: No definition found for declaration -(Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread [WARNING]: No definition found for declaration -(Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread [WARNING]: Generated declaration '__builtin_va_list' starts with '_' and therefore will be private. [INFO] : Finished, Bindings generated in /Users/brett/Documents/GitHub/codelabs/ffigen_codelab/step_05/lib/duktape_bindings_generated.dart
คุณจะเห็นคำเตือนที่แตกต่างกันในแต่ละระบบปฏิบัติการ คุณไม่ต้องสนใจข้อผิดพลาดเหล่านี้ในตอนนี้ เนื่องจาก Duktape 2.7.0 ทราบว่าคอมไพล์ด้วย clang
ใน Windows, Linux และ macOS
กำหนดค่า CMake
CMake เป็นระบบการสร้างระบบการต่อยอด ปลั๊กอินนี้ใช้ CMake เพื่อสร้างระบบการบิลด์สำหรับ Android, Windows และ Linux เพื่อรวม Duktape ไว้ในไบนารี Flutter ที่สร้างขึ้น คุณต้องแก้ไขไฟล์การกำหนดค่า CMake ที่เทมเพลตสร้างขึ้น ดังนี้
src/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(ffigen_app_library VERSION 0.0.1 LANGUAGES C)
add_library(ffigen_app SHARED
duktape.c # Modify
)
set_target_properties(ffigen_app PROPERTIES
PUBLIC_HEADER duktape.h # Modify
PRIVATE_HEADER duk_config.h # Add
OUTPUT_NAME "ffigen_app" # Add
)
# Add from here...
if (WIN32)
set_target_properties(ffigen_app PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
endif (WIN32)
# ... to here.
target_compile_definitions(ffigen_app PUBLIC DART_SHARED_LIB)
if (ANDROID)
# Support Android 15 16k page size
target_link_options(ffigen_app PRIVATE "-Wl,-z,max-page-size=16384")
endif()
การกําหนดค่า CMake จะเพิ่มไฟล์ซอร์สโค้ด และที่สำคัญกว่านั้นคือแก้ไขลักษณะการทํางานเริ่มต้นของไฟล์ไลบรารีที่สร้างขึ้นบน Windows เพื่อส่งออกสัญลักษณ์ C ทั้งหมดโดยค่าเริ่มต้น นี่เป็นวิธีแก้ปัญหาของ CMake เพื่อช่วยพอร์ตไลบรารีสไตล์ Unix ซึ่งก็คือ Duktape ไปยังโลกของ Windows
แทนที่เนื้อหาของ lib/ffigen_app.dart
ด้วยข้อมูลต่อไปนี้
lib/ffigen_app.dart
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'duktape_bindings_generated.dart';
const String _libName = 'ffigen_app';
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
final DuktapeBindings _bindings = DuktapeBindings(_dylib);
class Duktape {
Duktape() {
ctx = _bindings.duk_create_heap(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
);
}
void evalString(String jsCode) {
var nativeUtf8 = jsCode.toNativeUtf8();
_bindings.duk_eval_raw(
ctx,
nativeUtf8.cast<Char>(),
0,
0 |
DUK_COMPILE_EVAL |
DUK_COMPILE_SAFE |
DUK_COMPILE_NOSOURCE |
DUK_COMPILE_STRLEN |
DUK_COMPILE_NOFILENAME,
);
ffi.malloc.free(nativeUtf8);
}
int getInt(int index) {
return _bindings.duk_get_int(ctx, index);
}
void dispose() {
_bindings.duk_destroy_heap(ctx);
ctx = nullptr;
}
late Pointer<duk_hthread> ctx;
}
ไฟล์นี้มีหน้าที่โหลดไฟล์ไลบรารีลิงก์แบบไดนามิก (.so
สำหรับ Linux และ Android, .dll
สำหรับ Windows) และจัดเตรียมตัวแฝงซึ่งแสดงอินเทอร์เฟซแบบ Dart มากขึ้นต่อโค้ด C ที่อยู่เบื้องหลัง
เนื่องจากไฟล์นี้จะนำเข้าแพ็กเกจ ffi
โดยตรง คุณจึงต้องย้ายแพ็กเกจจาก dev_dependencies
ไปยัง dependencies
วิธีลัดในการดำเนินการนี้คือให้เรียกใช้คําสั่งต่อไปนี้
dart pub add ffi
แทนที่เนื้อหาของ main.dart
ของตัวอย่างด้วยข้อมูลต่อไปนี้
example/lib/main.dart
import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
const String jsCode = '1+2';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Duktape duktape;
String output = '';
@override
void initState() {
super.initState();
duktape = Duktape();
setState(() {
output = 'Initialized Duktape';
});
}
@override
void dispose() {
duktape.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Duktape Test')),
body: Center(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(output, style: textStyle, textAlign: TextAlign.center),
spacerSmall,
ElevatedButton(
child: const Text('Run JavaScript'),
onPressed: () {
duktape.evalString(jsCode);
setState(() {
output = '$jsCode => ${duktape.getInt(-1)}';
});
},
),
],
),
),
),
),
);
}
}
ตอนนี้คุณเรียกใช้แอปตัวอย่างอีกครั้งได้โดยใช้สิ่งต่อไปนี้
cd example flutter run
คุณควรเห็นแอปทํางานดังต่อไปนี้
ภาพหน้าจอ 2 ภาพนี้แสดงภาพก่อนและหลังการกดปุ่มเรียกใช้ JavaScript ตัวอย่างนี้แสดงการเรียกใช้โค้ด JavaScript จาก Dart และแสดงผลลัพธ์บนหน้าจอ
Android
Android เป็นระบบปฏิบัติการที่ใช้เคอร์เนล Linux และคล้ายกับระบบปฏิบัติการ Linux บนเดสก์ท็อป ระบบการคอมไพล์ CMake สามารถซ่อนความแตกต่างส่วนใหญ่ระหว่าง 2 แพลตฟอร์มนี้ได้ หากต้องการสร้างและเรียกใช้บน Android ให้ตรวจสอบว่าโปรแกรมจำลอง Android ทำงานอยู่ (หรืออุปกรณ์ Android เชื่อมต่ออยู่) เรียกใช้แอป เช่น
cd example flutter run -d emulator-5554
ตอนนี้คุณควรเห็นแอปตัวอย่างที่ทำงานบน Android
6 ใช้ Duktape ใน macOS และ iOS
ตอนนี้ถึงเวลาทำให้ปลั๊กอินทำงานใน macOS และ iOS ซึ่งเป็นระบบปฏิบัติการ 2 ระบบที่เกี่ยวข้องกัน เริ่มต้นด้วย macOS แม้ว่า CMake จะรองรับ macOS และ iOS แต่คุณจะใช้งานที่ทําสําหรับ Linux และ Android ซ้ำไม่ได้ เนื่องจาก Flutter ใน macOS และ iOS ใช้ CocoaPods เพื่อนําเข้าไลบรารี
ล้าง
ในขั้นตอนก่อนหน้า คุณได้สร้างแอปพลิเคชันที่ใช้งานได้สำหรับ Android, Windows และ Linux อย่างไรก็ตาม ยังมีไฟล์ 2-3 ไฟล์ที่เหลืออยู่จากเทมเพลตเดิมที่คุณต้องล้างออก นำออกเลยโดยทำดังนี้
rm src/ffigen_app.c rm src/ffigen_app.h rm ios/Classes/ffigen_app.c rm macos/Classes/ffigen_app.c
macOS
Flutter ในแพลตฟอร์ม macOS ใช้ CocoaPods เพื่อนำเข้าโค้ด C และ C++ ซึ่งหมายความว่าต้องผสานรวมแพ็กเกจนี้เข้ากับโครงสร้างพื้นฐานการสร้างของ CocoaPods หากต้องการใช้โค้ด C ที่คุณกำหนดค่าไว้ให้สร้างด้วย CMake ในขั้นตอนก่อนหน้าซ้ำ คุณจะต้องเพิ่มไฟล์การส่งต่อไฟล์เดียวในโปรแกรมรันไทม์แพลตฟอร์ม macOS
macos/Classes/duktape.c
#include "../../src/duktape.c"
ไฟล์นี้ใช้ความสามารถของ C preprocessor เพื่อรวมซอร์สโค้ดจากซอร์สโค้ดเนทีฟที่คุณตั้งค่าไว้ในขั้นตอนก่อนหน้า ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานของแอปได้ที่ macos/ffigen_app.podspec
ตอนนี้การเรียกใช้แอปพลิเคชันนี้เป็นไปตามรูปแบบเดียวกับที่คุณเห็นใน Windows และ Linux
cd example flutter run -d macos
iOS
iOS ต้องใช้ไฟล์ C ที่ส่งต่อไฟล์เดียวด้วย ซึ่งคล้ายกับการตั้งค่า macOS
ios/Classes/duktape.c
#include "../../src/duktape.c"
ไฟล์เดียวนี้จะช่วยให้ปลั๊กอินของคุณกําหนดค่าให้ทํางานบน iOS ได้ด้วย เรียกใช้ตามปกติ
flutter run -d iPhone
ยินดีด้วย คุณได้ผสานรวมโค้ดที่มาพร้อมเครื่องใน 5 แพลตฟอร์มเรียบร้อยแล้ว นี่เป็นเหตุผลที่ควรฉลอง หรืออาจเป็นอินเทอร์เฟซผู้ใช้ที่ใช้งานได้จริงมากขึ้น ซึ่งคุณจะสร้างในขั้นตอนถัดไป
7 ใช้ Read Eval Print Loop
การโต้ตอบกับภาษาโปรแกรมจะสนุกขึ้นมากในสภาพแวดล้อมแบบอินเทอร์แอกทีฟที่รวดเร็ว การใช้งานเดิมของสภาพแวดล้อมดังกล่าวคือ Read Eval Print Loop (REPL) ของ LISP คุณจะใช้สิ่งที่คล้ายกับ Duktape ในขั้นตอนนี้
เตรียมสิ่งต่างๆ ให้พร้อมใช้งานจริง
โค้ดปัจจุบันที่โต้ตอบกับไลบรารี Duktape C ถือว่าไม่มีปัญหาใดๆ และจะไม่โหลดไลบรารีลิงก์แบบไดนามิกของ Duktape เมื่ออยู่ระหว่างการทดสอบ คุณต้องทําการเปลี่ยนแปลงบางอย่างใน lib/ffigen_app.dart
เพื่อให้การผสานรวมนี้พร้อมใช้งานจริง
lib/ffigen_app.dart
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p; // Add this import
import 'duktape_bindings_generated.dart';
const String _libName = 'ffigen_app';
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return DynamicLibrary.open(
'build/macos/Build/Products/Debug/$_libName/$_libName.framework/$_libName',
);
}
// To here.
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return DynamicLibrary.open(
'build/linux/x64/debug/bundle/lib/lib$_libName.so',
);
}
// To here.
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return switch (Abi.current()) {
Abi.windowsArm64 => DynamicLibrary.open(
p.canonicalize(
p.join(r'build\windows\arm64\runner\Debug', '$_libName.dll'),
),
),
Abi.windowsX64 => DynamicLibrary.open(
p.canonicalize(
p.join(r'build\windows\x64\runner\Debug', '$_libName.dll'),
),
),
_ => throw 'Unsupported platform',
};
}
// To here.
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
final DuktapeBindings _bindings = DuktapeBindings(_dylib);
class Duktape {
Duktape() {
ctx = _bindings.duk_create_heap(
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
);
}
// Modify this function
String evalString(String jsCode) {
var nativeUtf8 = jsCode.toNativeUtf8();
final evalResult = _bindings.duk_eval_raw(
ctx,
nativeUtf8.cast<Char>(),
0,
0 |
DUK_COMPILE_EVAL |
DUK_COMPILE_SAFE |
DUK_COMPILE_NOSOURCE |
DUK_COMPILE_STRLEN |
DUK_COMPILE_NOFILENAME,
);
ffi.malloc.free(nativeUtf8);
if (evalResult != 0) {
throw _retrieveTopOfStackAsString();
}
return _retrieveTopOfStackAsString();
}
// Add this function
String _retrieveTopOfStackAsString() {
Pointer<Size> outLengthPtr = ffi.calloc<Size>();
final errorStrPtr = _bindings.duk_safe_to_lstring(ctx, -1, outLengthPtr);
final returnVal = errorStrPtr.cast<ffi.Utf8>().toDartString(
length: outLengthPtr.value,
);
ffi.calloc.free(outLengthPtr);
return returnVal;
}
void dispose() {
_bindings.duk_destroy_heap(ctx);
ctx = nullptr;
}
late Pointer<duk_hthread> ctx;
}
ซึ่งต้องใช้การเพิ่มแพ็กเกจ path
flutter pub add path
มีการขยายโค้ดเพื่อโหลดไลบรารีลิงก์แบบไดนามิกเพื่อจัดการกรณีที่มีการใช้ปลั๊กอินในโปรแกรมรันทดสอบ ซึ่งจะช่วยให้เขียนการทดสอบการผสานรวมที่ใช้ API นี้เป็นการทดสอบ Flutter ได้ โค้ดสำหรับประเมินสตริงโค้ด JavaScript ได้รับการขยายให้จัดการกับเงื่อนไขข้อผิดพลาดได้อย่างถูกต้อง เช่น โค้ดที่ไม่สมบูรณ์หรือไม่ถูกต้อง โค้ดเพิ่มเติมนี้แสดงวิธีจัดการสถานการณ์ที่ระบบแสดงผลสตริงเป็นอาร์เรย์ไบต์และต้องแปลงเป็นสตริง Dart
เพิ่มแพ็กเกจ
ในการสร้าง REPL คุณจะแสดงการโต้ตอบระหว่างผู้ใช้กับเครื่องมือรันไทม์ JavaScript ของ Duktape ผู้ใช้ป้อนบรรทัดโค้ด แล้ว Duktape จะตอบกลับด้วยผลลัพธ์ของการคํานวณหรือข้อยกเว้น คุณจะใช้ freezed
เพื่อลดจำนวนโค้ดที่ซ้ำกันซึ่งต้องเขียน นอกจากนี้ คุณจะใช้ google_fonts
เพื่อทำให้เนื้อหาที่แสดงมีความสอดคล้องกับธีมมากขึ้น และ flutter_riverpod
เพื่อจัดการสถานะได้ด้วย
เพิ่มการอ้างอิงที่จำเป็นลงในแอปตัวอย่าง
cd example flutter pub add flutter_riverpod freezed_annotation google_fonts flutter pub add -d build_runner freezed
ถัดไป ให้สร้างไฟล์เพื่อบันทึกการโต้ตอบ REPL โดยทำดังนี้
example/lib/duktape_message.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'duktape_message.freezed.dart';
@freezed
class DuktapeMessage with _$DuktapeMessage {
factory DuktapeMessage.evaluate(String code) = DuktapeMessageCode;
factory DuktapeMessage.response(String result) = DuktapeMessageResponse;
factory DuktapeMessage.error(String log) = DuktapeMessageError;
}
คลาสนี้ใช้ฟีเจอร์ประเภทยูเนียนของ freezed
เพื่อเปิดใช้นิพจน์ที่ปลอดภัยตามประเภทของรูปร่างของแต่ละบรรทัดที่แสดงใน REPL เป็นหนึ่งใน 3 ประเภท เมื่อถึงขั้นตอนนี้ โค้ดของคุณอาจแสดงข้อผิดพลาดรูปแบบใดรูปแบบหนึ่ง เนื่องจากมีโค้ดเพิ่มเติมที่ต้องสร้างขึ้น โปรดดำเนินการดังต่อไปนี้
flutter pub run build_runner build
ซึ่งจะสร้างไฟล์ example/lib/duktape_message.freezed.dart
ที่รหัสที่คุณเพิ่งพิมพ์ไว้ใช้
ถัดไป คุณจะต้องทำการแก้ไข 2 รายการในไฟล์การกําหนดค่า macOS เพื่อเปิดใช้ google_fonts
ในการส่งคําขอข้อมูลแบบอักษรผ่านเครือข่าย
example/macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- ...to here -->
</dict>
</plist>
example/macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- ...to here -->
</dict>
</plist>
สร้าง REPL
เมื่ออัปเดตเลเยอร์การผสานรวมเพื่อจัดการข้อผิดพลาดและสร้างการนําเสนอข้อมูลสําหรับการโต้ตอบแล้ว ก็ถึงเวลาสร้างอินเทอร์เฟซผู้ใช้ของแอปตัวอย่าง
example/lib/main.dart
import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'duktape_message.dart';
void main() {
runApp(const ProviderScope(child: DuktapeApp()));
}
final duktapeMessagesProvider =
StateNotifierProvider<DuktapeMessageNotifier, List<DuktapeMessage>>((ref) {
return DuktapeMessageNotifier(messages: <DuktapeMessage>[]);
});
class DuktapeMessageNotifier extends StateNotifier<List<DuktapeMessage>> {
DuktapeMessageNotifier({required List<DuktapeMessage> messages})
: duktape = Duktape(),
super(messages);
final Duktape duktape;
void eval(String code) {
state = [DuktapeMessage.evaluate(code), ...state];
try {
final response = duktape.evalString(code);
state = [DuktapeMessage.response(response), ...state];
} catch (e) {
state = [DuktapeMessage.error('$e'), ...state];
}
}
}
class DuktapeApp extends StatelessWidget {
const DuktapeApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(title: 'Duktape App', home: DuktapeRepl());
}
}
class DuktapeRepl extends ConsumerStatefulWidget {
const DuktapeRepl({super.key});
@override
ConsumerState<DuktapeRepl> createState() => _DuktapeReplState();
}
class _DuktapeReplState extends ConsumerState<DuktapeRepl> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
var _isComposing = false;
void _handleSubmitted(String text) {
_controller.clear();
setState(() {
_isComposing = false;
});
setState(() {
ref.read(duktapeMessagesProvider.notifier).eval(text);
});
_focusNode.requestFocus();
}
@override
Widget build(BuildContext context) {
final messages = ref.watch(duktapeMessagesProvider);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('Duktape REPL'),
elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
),
body: Column(
children: [
Flexible(
child: Ink(
color: Theme.of(context).scaffoldBackgroundColor,
child: SafeArea(
bottom: false,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (context, idx) {
return switch (messages[idx]) {
DuktapeMessageCode code => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'> ${code.code}',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
),
),
),
DuktapeMessageResponse response => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'= ${response.result}',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
color: Colors.blue[800],
),
),
),
DuktapeMessageError error => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
error.log,
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleSmall,
color: Colors.red[800],
fontWeight: FontWeight.bold,
),
),
),
DuktapeMessage message => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'Unhandled message $message',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleSmall,
color: Colors.red[800],
fontWeight: FontWeight.bold,
),
),
),
};
},
itemCount: messages.length,
),
),
),
),
const Divider(height: 1.0),
SafeArea(
top: false,
child: Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
),
],
),
);
}
Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Text('>', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(width: 4),
Flexible(
child: TextField(
controller: _controller,
decoration: const InputDecoration(border: InputBorder.none),
onChanged: (text) {
setState(() {
_isComposing = text.isNotEmpty;
});
},
onSubmitted: _isComposing ? _handleSubmitted : null,
focusNode: _focusNode,
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
icon: const Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_controller.text)
: null,
),
),
],
),
),
);
}
}
โค้ดนี้มีหลายอย่างเกิดขึ้น แต่อธิบายทั้งหมดนั้นอยู่นอกเหนือขอบเขตของโค้ดแล็บนี้ เราขอแนะนำให้คุณเรียกใช้โค้ด แล้วทำการแก้ไขโค้ดหลังจากอ่านเอกสารประกอบที่เหมาะสม
cd example flutter run
8 ขอแสดงความยินดี
ยินดีด้วย คุณสร้างปลั๊กอิน Flutter ที่ใช้ FFI สำหรับ Windows, macOS, Linux, Android และ iOS เรียบร้อยแล้ว
หลังจากสร้างปลั๊กอินแล้ว คุณอาจต้องการแชร์ปลั๊กอินทางออนไลน์เพื่อให้ผู้อื่นนำไปใช้ได้ ดูเอกสารประกอบฉบับเต็มเกี่ยวกับการเผยแพร่ปลั๊กอินไปยัง pub.dev ได้ในการพัฒนาแพ็กเกจปลั๊กอิน